diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 62e19d866..8c1d16e8d 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/CHANGELOG.md b/CHANGELOG.md index 693d32bd9..da54bc365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +# 3.1.0 - Unreleased + +### Upgrade Steps -- Required + +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()`. + +### 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` +- `Furnace` + - Allow melting while frozen + +## Plugins + +### Assets + +- Remove `lotPrice()` +- Alter `price().high` to decay upwards to 3x over the price timeout + # 3.0.1 ### Upgrade steps @@ -8,6 +34,8 @@ Update `BackingManager`, both `RevenueTraders` (rTokenTrader/rsrTrader), and cal # 3.0.0 +Bump solidity version to 0.8.19 + ### Upgrade Steps #### Required Steps @@ -38,9 +66,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 @@ -180,10 +206,10 @@ Remove `FacadeMonitor` - now redundant with `nextRecollateralizationAuction()` a - `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)` +- 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 diff --git a/README.md b/README.md index 1c569eb49..e8ab78fae 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/common/configuration.ts b/common/configuration.ts index e6e1f846e..f17059cb0 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/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 562274242..37ec3dc4b 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -116,11 +116,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/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 10f60d918..a42d5c613 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 d16c6ab6d..9b57538a4 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 } @@ -97,7 +99,6 @@ contract FacadeTest is IFacadeTest { // Poke Main reg.refresh(); - main.furnace().melt(); address backingManager = address(main.backingManager()); IERC20 rsr = main.rsr(); @@ -134,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/interfaces/IAsset.sol b/contracts/interfaces/IAsset.sol index bd796190a..b5423ab2b 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/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index caeaac2f3..add18d69b 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/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 42bb8bf09..e43cf6735 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 20e2ed0cb..fcaeac2c1 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/mixins/Versioned.sol b/contracts/mixins/Versioned.sol index 751855112..c70c7a885 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/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index c22df732c..946534141 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"); @@ -166,16 +164,20 @@ 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 // 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/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 357b0a725..03e99b140 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(); @@ -368,26 +370,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 +384,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 02b88de2f..b53c2e041 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(); @@ -239,6 +239,11 @@ contract BrokerP0 is ComponentP0, IBroker { "dutch auctions disabled for token pair" ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); + require( + priceNotDecayed(req.sell) && priceNotDecayed(req.buy), + "dutch auctions require live prices" + ); + DutchTrade trade = DutchTrade(Clones.clone(address(dutchTradeImplementation))); trades[address(trade)] = true; @@ -251,4 +256,10 @@ contract BrokerP0 is ComponentP0, IBroker { trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength, prices); return trade; } + + /// @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/Distributor.sol b/contracts/p0/Distributor.sol index d305e9b52..264d7bfe7 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); @@ -58,13 +63,15 @@ 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. // This rounds "early", and that's deliberate! + bool accountRewards = false; + for (uint256 i = 0; i < destinations.length(); i++) { address addrTo = destinations.at(i); @@ -76,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/p0/Furnace.sol b/contracts/p0/Furnace.sol index aa99a8140..ea0a404a2 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 9493b72c5..1859ad8ec 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/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index a62cf8bd1..8fecf2eb7 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -32,14 +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) - notTradingPausedOrFrozen - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public override(ITrading, TradingP0) returns (ITrade trade) { trade = super.settleTrade(sell); - _distributeTokenToBuy(); + + // 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 + } // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -80,10 +80,18 @@ 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); - (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 +107,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 a71df6c02..b9dc3ca78 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] @@ -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,15 @@ 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. + // low decays down; high decays up + + // 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 +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 (lotLow, 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 @@ -453,19 +447,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 +470,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 +481,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 +506,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/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 098e47f93..c556a9612 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 a64ff3f41..7763ddb77 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)); @@ -212,20 +210,23 @@ 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 // 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/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 2cb493d1a..6a0753c1b 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(); @@ -309,27 +311,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 +324,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); @@ -421,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; } } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index cfc6100a9..0111d25bc 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -63,6 +63,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 @@ -81,10 +85,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_); @@ -93,6 +94,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 @@ -127,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(); @@ -256,6 +265,11 @@ contract BrokerP1 is ComponentP1, IBroker { "dutch auctions disabled for token pair" ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); + require( + priceNotDecayed(req.sell) && priceNotDecayed(req.buy), + "dutch auctions require live prices" + ); + DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); trades[address(trade)] = true; @@ -270,10 +284,15 @@ contract BrokerP1 is ComponentP1, IBroker { return trade; } + /// @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); + } + /** * @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[42] private __gap; + uint256[41] private __gap; } diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index ca818f5a1..776e19fe5 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -57,13 +57,17 @@ 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); } struct Transfer { - IERC20 erc20; address addrTo; uint256 amount; } @@ -94,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. @@ -107,6 +111,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); @@ -118,15 +124,13 @@ 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({ - erc20: erc20, - addrTo: addrTo, - amount: transferAmt - }); + transfers[numTransfers] = Transfer({ addrTo: addrTo, amount: transferAmt }); numTransfers++; } emit RevenueDistributed(erc20, caller, amount); @@ -134,7 +138,16 @@ 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 + if (accountRewards) { + if (isRSR) { + main.stRSR().payoutRewards(); + } else { + main.furnace().melt(); + } } } diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index 923ba3373..63dcc695d 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 43bddcaed..21781ca08 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 77f15bef5..cfbbdcd92 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 43253c6b0..998bdc951 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -53,7 +53,12 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant - _distributeTokenToBuy(); + + // 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 + } // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -107,6 +112,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 @@ -123,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(); @@ -135,7 +144,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 +156,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 dd86e45ca..ac5deeb3f 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 @@ -145,8 +146,8 @@ library RecollateralizationLibP1 { // - 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 - // 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 +165,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 +199,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 +245,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 +304,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 +344,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 +363,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 +381,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 +406,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/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index f0921dd51..8d3c8e01c 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] diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 24b2044d3..7b38c7c30 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -119,7 +119,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 // Set allowance via custom approval -- first sets allowance to 0, then sets allowance // to either the requested amount or the maximum possible amount, if that fails. diff --git a/contracts/plugins/assets/AppreciatingFiatCollateral.sol b/contracts/plugins/assets/AppreciatingFiatCollateral.sol index bf7cef602..60e575cf7 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/Asset.sol b/contracts/plugins/assets/Asset.sol index 1bb044c23..3b858a9ea 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -7,10 +7,14 @@ 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; + uint192 public constant MAX_HIGH_PRICE_BUFFER = 2 * FIX_ONE; // {UoA/tok} 200% + AggregatorV3Interface public immutable chainlinkFeed; // {UoA/tok} IERC20Metadata public immutable erc20; @@ -38,7 +42,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_, @@ -60,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 @@ -108,54 +112,60 @@ 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 + /// @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 - 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 + // unpriced after a full timeout + return (0, FIX_MAX); } else { // oracleTimeout <= delta <= oracleTimeout + priceTimeout - // {1} = {s} / {s} - uint192 lotMultiplier = divuu(oracleTimeout + priceTimeout - delta, priceTimeout); - + // Decay _high upwards to 3x savedHighPrice // {UoA/tok} = {UoA/tok} * {1} - lotLow = savedLowPrice.mul(lotMultiplier); - lotHigh = savedHighPrice.mul(lotMultiplier); + _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(lotLow <= lotHigh); + assert(_low <= _high); } /// @return {tok} The balance of the ERC20 in whole tokens diff --git a/contracts/plugins/assets/EURFiatCollateral.sol b/contracts/plugins/assets/EURFiatCollateral.sol index 67d0c12f3..dfc36ff73 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 9110117bc..d3afad43c 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; @@ -122,7 +126,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/L2LSDCollateral.sol b/contracts/plugins/assets/L2LSDCollateral.sol index 0fc8e4088..60b0bd832 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; @@ -52,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/NonFiatCollateral.sol b/contracts/plugins/assets/NonFiatCollateral.sol index 2e6b3c531..1923dea24 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/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 68a9da986..f82f2ee18 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -19,11 +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; @@ -38,9 +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(); @@ -55,10 +61,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 @@ -79,21 +83,21 @@ 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 lotPrice() + // No need to save lastPrice; can piggyback off the backing collateral's saved prices + + 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 /// @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 @@ -102,22 +106,6 @@ 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) { - 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 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 @@ -143,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 ( @@ -158,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( @@ -178,7 +173,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} @@ -196,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/VersionedAsset.sol b/contracts/plugins/assets/VersionedAsset.sol index ac8371e7f..b36945769 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/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol index 2edfd5d65..ba1843351 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 f6e98c267..14e72a72c 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 594db5465..59e921e77 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 5c190e605..40eb3a9d6 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 b745028f5..4e98b7c3f 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/cbeth/README.md b/contracts/plugins/assets/cbeth/README.md index 15deec4f2..351074009 100644 --- a/contracts/plugins/assets/cbeth/README.md +++ b/contracts/plugins/assets/cbeth/README.md @@ -15,6 +15,7 @@ This plugin allows `CBETH` holders to use their tokens as collateral in the Rese ### Functions #### refPerTok {ref/tok} + 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/compoundv2/CTokenFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol index a60744893..ce76a7263 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 f0a44584b..3d7dcae18 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 5e7bd1238..92f8a2d20 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_; @@ -76,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 f5706f75e..8a21dfda9 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/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol index 420e002f4..780a083a8 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/assets/dsr/SDaiCollateral.sol b/contracts/plugins/assets/dsr/SDaiCollateral.sol index 8e7643575..5401b2ad5 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/erc20/RewardableERC20.sol b/contracts/plugins/assets/erc20/RewardableERC20.sol index 58fd23855..6f0a606b6 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/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol index e2a4ec927..6ae34a21a 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 284f717c2..3966e66ea 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/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol index 4697ec0da..c318a047e 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 783896f2c..9267f40e7 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 5959b944e..248c24084 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 f7f438665..97c58aaef 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 8aaf4b238..d31d7b04d 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/contracts/plugins/governance/Governance.sol b/contracts/plugins/governance/Governance.sol index c20978fa0..96de23191 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/contracts/plugins/mocks/AssetMock.sol b/contracts/plugins/mocks/AssetMock.sol index b6abe6fff..0396a5ea3 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; @@ -12,7 +14,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_, @@ -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/contracts/plugins/mocks/RevenueTraderBackComp.sol b/contracts/plugins/mocks/RevenueTraderBackComp.sol index ed76f5334..73069f15a 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/docs/collateral.md b/docs/collateral.md index 7c95883f5..9655107da 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -42,16 +42,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); @@ -219,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 @@ -252,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/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()`. +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. @@ -299,7 +294,7 @@ The values returned by the following view methods should never change: 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()`/`lotPrice()`/`refresh()`) +- `tryPrice()` (not on the ICollateral interface; used by `price()`/`refresh()`) - `refPerTok()` - `targetPerRef()` @@ -362,21 +357,13 @@ Should never revert. Should return the tightest possible lower and upper estimate for the price of the token on secondary markets. -Lower estimate must be <= upper estimate. - -Should return `(0, FIX_MAX)` if pricing data is unavailable or stale. - -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. +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. -The low estimate should be nonzero while the asset is worth selling. +Under no price data, the low estimate shoulddecay downwards and high estimate upwards. + +Should return `(0, FIX_MAX)` if pricing data is _completely_ unavailable or stale. Should be gas-efficient. diff --git a/docs/deployment.md b/docs/deployment.md index 6fbf56502..98c96678e 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/pause-freeze-states.md b/docs/pause-freeze-states.md new file mode 100644 index 000000000..17b2785fc --- /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: | :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: | +| `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/docs/recollateralization.md b/docs/recollateralization.md index aecb345c9..06cf83659 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/addresses/84531-RTKN-tmp-deployments.json b/scripts/addresses/84531-RTKN-tmp-deployments.json new file mode 100644 index 000000000..e7ec7dc68 --- /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/confirmation/1_confirm_assets.ts b/scripts/confirmation/1_confirm_assets.ts index 0c62bfbac..b5ea27b8e 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/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts b/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts index e67a33f60..f33da05e8 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 cab49a551..93a8a6939 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 cd8fdaa33..5a58c3bea 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 f0db202b9..80eb3f6c1 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 1c5aaa57c..cb7eb2d5d 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 e7fbc9851..f930201a2 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 2d56f9d3f..2d4eb8112 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 eab685015..18984099f 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 d328a311d..89b2464c5 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 dae7875b3..e7b53d4f5 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 72d0f7deb..4a2c1c6ea 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 97a8fc4f2..bc52c467c 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 ab7131667..000000000 --- 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 3f4755ff7..21e78893a 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 873b8fbcc..a05ac5bbc 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 e886d6d80..c2d760a2f 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 f97882d21..e62840d7e 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 b2d6462d6..6b9f415d0 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 f3b6e3e61..000000000 --- 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 26ab8341a..d06853b7c 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 af7258ec4..6acbcccf8 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 bc6a8b160..30884eae2 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 535b4fd9f..4c7457465 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 ==== @@ -46,7 +46,6 @@ async function main() { const MorphoTokenisedDepositFactory = await ethers.getContractFactory( 'MorphoAaveV2TokenisedDeposit' ) - const maUSDT = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, @@ -127,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 @@ -185,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 @@ -194,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()) @@ -208,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 @@ -237,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 @@ -246,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 7f8b30899..d90520b97 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,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout, combinedError } from '../../utils' +import { priceTimeout, combinedError } from '../../utils' import { RethCollateral } from '../../../../typechain' import { ContractFactory } from 'ethers' @@ -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 5db4436ea..dfd837767 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 8a43556a5..4ac4e4c6c 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 84dad2be5..a8c2083e0 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 f857f2887..fbcfe84d2 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 @@ -99,7 +93,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( @@ -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 edb092d1a..3a373573d 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 1486c37ca..3ffce9a0a 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 4e58ad88d..9b52d6323 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 3c22ae255..cd7ffc0e8 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 440f8854b..fedb2d418 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 771f6bc76..400c23d10 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 8c48da0e5..000000000 --- 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 ce1120f61..3f4b66190 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 60be29f1e..e1b433bbd 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 a48df02d1..43d2172f1 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 2f5c53b2c..000000000 --- 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 c3a6cb314..d0eb672ef 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 62c138928..09a6eceb3 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 ba7658f5a..4f9e6d832 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 324a08185..077cc76e0 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 c0b73b2fb..b84c9aad5 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/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index b32aac277..6014dfcfb 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -36,7 +36,7 @@ async function main() { // even if some portions have already been verified // Phase 1- Common - let scripts = [ + const scripts = [ '0_verify_libraries.ts', '1_verify_implementations.ts', '2_verify_rsrAsset.ts', diff --git a/test/Broker.test.ts b/test/Broker.test.ts index ff345cabd..633d1fed4 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' @@ -473,6 +473,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) @@ -491,6 +492,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) @@ -572,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', () => { @@ -1271,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, @@ -1436,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 @@ -1448,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 @@ -1660,6 +1640,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') @@ -1687,6 +1675,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) ) @@ -1695,6 +1686,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) ) @@ -1703,6 +1697,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 207263b70..9da2b8398 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) }) @@ -481,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 @@ -505,7 +511,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 @@ -551,98 +560,98 @@ describe('FacadeRead + FacadeAct contracts', () => { }) it('Should return revenue + chain into FacadeAct.runRevenueAuctions', async () => { - const traders = [rTokenTrader, rsrTrader] - const initialPrice = await usdcAsset.price() + // 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 [lotLow] = await usdcAsset.lotPrice() - expect(lotLow).to.equal(initialPrice[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.lotPrice() - 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 + } + + // 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') - // Settle and start new auction - await facadeAct.runRevenueAuctions(trader.address, erc20sToStart, erc20sToStart, [ - TradeKind.DUTCH_AUCTION, - ]) + // 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)) - // Send additional revenues - await token.connect(addr1).transfer(trader.address, tokenSurplus) + // 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 + ) - // Call revenueOverview, cannot open new auctions - ;[erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - expect(canStart).to.eql(Array(8).fill(false)) + // 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 () => { @@ -892,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) @@ -1139,10 +1152,7 @@ describe('FacadeRead + FacadeAct contracts', () => { expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) // Advance time till auction ended - await advanceBlocks(2 + auctionLength / 12) - - // Settleable now - expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(1) + await advanceBlocks(1 + auctionLength / 12) // Settle and start new auction - Will retry await expectEvents( @@ -1167,18 +1177,6 @@ describe('FacadeRead + FacadeAct contracts', () => { }, ] ) - - // Nothing should be settleable - expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) - - // Advance time till auction ended - await advanceBlocks(2 + auctionLength / 12) - - // Settleable now - expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(1) - - // Should not revert, even when not starting new auctions - await facadeAct.runRevenueAuctions(rsrTrader.address, [token.address], [], []) }) it('Should handle other versions when running revenue auctions', async () => { diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index 97210ce74..9176c71ac 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/Furnace.test.ts b/test/Furnace.test.ts index 15776210b..84d16f32c 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/Governance.test.ts b/test/Governance.test.ts index 83dffb413..53b7f7d2f 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) + }) }) }) diff --git a/test/Main.test.ts b/test/Main.test.ts index 788d4eba4..de7d519b7 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -70,6 +70,8 @@ import { Implementation, IMPLEMENTATION, ORACLE_ERROR, + ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from './fixtures' @@ -1180,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(), @@ -1202,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(), @@ -1228,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(), @@ -1627,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(), @@ -1723,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(), @@ -1946,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(), @@ -2756,8 +2758,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) @@ -2799,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(), @@ -2812,6 +2816,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() @@ -2832,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(), @@ -2840,17 +2846,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 () => { @@ -2865,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(), @@ -2907,35 +2905,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( @@ -3116,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(), @@ -3160,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(), @@ -3190,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 f11c415f6..f5c8afa99 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), @@ -155,7 +155,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Recharge throttle await advanceTime(3600) - await advanceTime(await basketHandler.warmupPeriod()) // ==== Issue the "initial" rtoken supply to owner diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index e806d4b31..30796f9b8 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(), @@ -1015,9 +1016,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 +1027,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 +1186,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 +1216,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') @@ -2148,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(), @@ -2212,7 +2210,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)) @@ -2255,64 +2253,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), [ { @@ -2341,17 +2282,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 @@ -2360,7 +2298,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, @@ -2373,11 +2311,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, @@ -2407,7 +2345,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) @@ -2418,11 +2356,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) @@ -2435,7 +2373,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, @@ -3311,7 +3249,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { collateral1.address, collateral0.address, issueAmount, - config.minTradeVolume, config.maxTradeSlippage ), bn('1e12') @@ -3375,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 427738693..3858ba429 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' @@ -59,6 +60,7 @@ import { REVENUE_HIDING, ORACLE_ERROR, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, } from './fixtures' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' @@ -554,7 +556,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,20 +564,36 @@ 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) 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( @@ -651,9 +669,113 @@ 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 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) + 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() + 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 +798,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 +1106,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') @@ -1036,26 +1162,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 () => { @@ -1100,7 +1226,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 @@ -1181,7 +1307,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, MAX_UINT192, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1192,7 +1318,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, rsr.address, MAX_UINT192, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1360,7 +1486,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1411,7 +1537,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 @@ -1559,7 +1685,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1593,7 +1719,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(), [ @@ -1758,7 +1884,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1791,7 +1917,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 @@ -1969,10 +2095,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 @@ -1990,6 +2112,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) @@ -2008,52 +2134,365 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('RevenueTraders only') }) - // 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) + // 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 () => { + // 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 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) + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + 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 whileImpersonating(rsrTrader.address, async (bmSigner) => { - await rsr.connect(bmSigner).approve(distributor.address, distAmount) - await distributor.connect(bmSigner).distribute(rsr.address, distAmount) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRToken, + buyAmount: minBuyAmtRToken, }) - // 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 () => { - // 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) + // Pause Trading + await main.connect(owner).pauseTrading() - // 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, + }, + ]) - await whileImpersonating(rTokenTrader.address, async (bmSigner) => { - await expect( - distributor.connect(bmSigner).distribute(rsr.address, bn(100)) - ).to.be.revertedWith('nothing to distribute') - }) + // 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) - // 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) + 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 () => { @@ -2244,11 +2683,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 @@ -2827,7 +3388,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(), @@ -2954,7 +3515,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rTokenAsset.address, collateral0.address, issueAmount, - config.minTradeVolume, config.maxTradeSlippage ) expect(actual).to.be.closeTo(expected, expected.div(bn('1e15'))) @@ -3014,7 +3574,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rTokenAsset.address, collateral0.address, issueAmount, - config.minTradeVolume, config.maxTradeSlippage ) expect(await rTokenTrader.tradesOpen()).to.equal(0) @@ -3791,6 +4350,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 () { @@ -3931,7 +4590,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index fc823d852..ceb61ab16 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -2,20 +2,20 @@ 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`; -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 848354f55..76f00fe01 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`] = `8464999`; 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 af06969a2..e905f3eec 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 06ba9d68c..cbee6c938 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`] = `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`] = `167045`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `223993`; -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 f50430c9b..78da6ac50 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`] = `782198`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `614457`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `609198`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `589230`; +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 9e0d532f8..236df3d70 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`] = `1364203`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1510705`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1499568`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `747331`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `751607`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1680908`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1656594`; -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`] = `1613640`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1599451`; -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`] = `1702037`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1687212`; -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 81fa8bb74..575c2d39f 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`] = `1008567`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1044877`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `773918`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `796514`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1181227`; +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`] = `367672`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `266512`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `317861`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `739718`; +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`] = `284880`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index dbc65bb91..3af605e7b 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`] = `151759`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; 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`] = `572011`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `606313`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `526015`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `536447`; diff --git a/test/fixtures.ts b/test/fixtures.ts index ff881e60d..6244d68ff 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -71,14 +71,16 @@ 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_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 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 @@ -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 7e067fa87..71ae4bf11 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' @@ -19,7 +20,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 +47,7 @@ import { MockV3Aggregator, NonFiatCollateral, RTokenAsset, + SelfReferentialCollateral, StaticATokenLM, TestIBackingManager, TestIBasketHandler, @@ -1082,7 +1091,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) @@ -1114,19 +1123,30 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, ORACLE_ERROR, networkConfig[chainId].tokens.stkAAVE || '', config.rTokenMaxTradeVolume, - MAX_ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) + 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) }) 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) @@ -1183,19 +1203,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_PRE_BUFFER, 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) }) @@ -1206,7 +1237,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) @@ -1258,18 +1289,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_PRE_BUFFER, 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 @@ -1279,7 +1321,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) @@ -1335,18 +1377,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_PRE_BUFFER, 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 @@ -1356,7 +1409,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) @@ -1403,32 +1456,35 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: wbtc.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) + 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 () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) // Compound await expectUnpriced(cWBTCCollateral.address) @@ -1480,36 +1536,39 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cWBTCVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, 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 () => { 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) @@ -1518,8 +1577,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 +1601,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_PRE_BUFFER, 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) }) @@ -1570,7 +1643,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) @@ -1621,7 +1694,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_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn('0'), delayUntilDefault, @@ -1629,12 +1702,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 @@ -1646,7 +1731,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) @@ -1692,22 +1777,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_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) + 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 diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 3d63f9e5b..355d92f00 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 @@ -752,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 @@ -764,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/__snapshots__/CTokenVaultGas.test.ts.snap b/test/integration/__snapshots__/CTokenVaultGas.test.ts.snap deleted file mode 100644 index a9ec5a85c..000000000 --- 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/integration/fixtures.ts b/test/integration/fixtures.ts index f22f9a1f1..206cfb2af 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 cf5b9d033..30df303ed 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 0a534ae9c..4dd32dc76 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, @@ -32,6 +34,7 @@ import { RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIFurnace, TestIRToken, USDCMock, UnpricedAssetMock, @@ -42,6 +45,7 @@ import { IMPLEMENTATION, Implementation, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, ORACLE_ERROR, PRICE_TIMEOUT, VERSION, @@ -86,6 +90,7 @@ describe('Assets contracts #fast', () => { let wallet: Wallet let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager + let furnace: TestIFurnace // Factory let AssetFactory: ContractFactory @@ -110,6 +115,7 @@ describe('Assets contracts #fast', () => { assetRegistry, backingManager, config, + furnace, rToken, rTokenAsset, } = await loadFixture(defaultFixture)) @@ -260,34 +266,51 @@ 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')) - // Should be unpriced + // Fallback prices should be initial prices + 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) await expectUnpriced(compAsset.address) await expectUnpriced(aaveAsset.address) - - // 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(rsrInitPrice[0]) - expect(lotHigh).to.eq(rsrInitPrice[1]) - ;[lotLow, lotHigh] = await aaveAsset.lotPrice() - 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.lotPrice() - expect(lotLow).to.eq(rTokenInitPrice[0]) - expect(lotHigh).to.eq(rTokenInitPrice[1]) }) it('Should return 0 price for RTokenAsset in full haircut scenario', async () => { @@ -317,12 +340,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 () => { @@ -349,35 +366,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 reverting edge cases for RToken', async () => { @@ -390,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, @@ -427,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, @@ -444,6 +485,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() @@ -494,7 +556,7 @@ describe('Assets contracts #fast', () => { ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -533,37 +595,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 () => { @@ -581,79 +641,71 @@ describe('Assets contracts #fast', () => { ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) // 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) }) }) @@ -724,9 +776,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 ff0441b43..e52458f39 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, @@ -50,6 +52,7 @@ import { Collateral, defaultFixture, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING, @@ -252,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, @@ -260,6 +263,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_PRE_BUFFER, + 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_PRE_BUFFER, + 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({ @@ -268,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), @@ -284,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), @@ -302,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), @@ -320,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, @@ -336,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, @@ -354,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, @@ -373,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, @@ -389,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, @@ -407,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), @@ -426,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, @@ -442,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, @@ -460,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, @@ -481,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, @@ -499,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, @@ -582,7 +623,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,22 +631,25 @@ 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.lotPrice() + let [lotLow, lotHigh] = await tokenCollateral.price() expect(lotLow).to.eq(compInitPrice[0]) expect(lotHigh).to.eq(compInitPrice[1]) - ;[lotLow, lotHigh] = await cTokenCollateral.lotPrice() + ;[lotLow, lotHigh] = await cTokenCollateral.price() expect(lotLow).to.eq(rsrInitPrice[0]) expect(lotHigh).to.eq(rsrInitPrice[1]) - ;[lotLow, lotHigh] = await aTokenCollateral.lotPrice() + ;[lotLow, lotHigh] = await aTokenCollateral.price() 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() @@ -616,38 +660,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) }) @@ -714,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, @@ -908,14 +970,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 () => { @@ -1109,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() @@ -1132,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') }) @@ -1152,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') }) @@ -1172,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') }) @@ -1192,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, @@ -1203,6 +1275,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_PRE_BUFFER, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: bn(0), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + targetUnitOracle.address, + ORACLE_TIMEOUT_PRE_BUFFER + ) + ).to.be.revertedWith('defaultThreshold zero') + }) + it('Should setup collateral correctly', async function () { // Non-Fiat Token expect(await nonFiatCollateral.isCollateral()).to.equal(true) @@ -1231,6 +1323,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) @@ -1240,26 +1334,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 () => { @@ -1275,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 ) ) @@ -1303,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 @@ -1364,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() @@ -1388,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') @@ -1409,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') @@ -1430,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') @@ -1451,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') @@ -1472,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, @@ -1484,6 +1589,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_PRE_BUFFER, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: bn(0), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + targetUnitOracle.address, + ORACLE_TIMEOUT_PRE_BUFFER, + REVENUE_HIDING + ) + ).to.be.revertedWith('defaultThreshold zero') + }) + it('Should setup collateral correctly', async function () { // Non-Fiat Token expect(await cTokenNonFiatCollateral.isCollateral()).to.equal(true) @@ -1522,47 +1648,128 @@ 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) + + // Check the other oracle's impact + await referenceUnitOracle.updateAnswer(bn('0')) + await expectExactPrice(cTokenNonFiatCollateral.address, initialPrice) + + // Advance past oracle timeout + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await referenceUnitOracle.updateAnswer(bn('0')) + await expectDecayedPrice(cTokenNonFiatCollateral.address) - // Revert if price is zero - Update the other Oracle + // Advance past price timeout + await advanceTime(PRICE_TIMEOUT.toString()) await referenceUnitOracle.updateAnswer(bn('0')) await expectUnpriced(cTokenNonFiatCollateral.address) + }) - // When refreshed, sets status to IFFY - await cTokenNonFiatCollateral.refresh() - 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('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('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { @@ -1610,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, @@ -1639,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, @@ -1681,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, @@ -1721,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, @@ -1739,9 +1946,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() @@ -1758,6 +1973,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 () => { @@ -1772,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, @@ -1822,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, @@ -1846,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, @@ -1866,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, @@ -1886,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, @@ -1950,13 +2171,90 @@ 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 () => { + 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('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('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { @@ -2004,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, @@ -2054,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, @@ -2077,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), @@ -2097,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, @@ -2117,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, @@ -2137,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, @@ -2148,6 +2446,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_PRE_BUFFER, + 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) @@ -2193,10 +2511,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() @@ -2211,6 +2531,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 () => { @@ -2226,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, @@ -2254,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, @@ -2329,15 +2661,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/RewardableERC20.test.ts b/test/plugins/RewardableERC20.test.ts index abc94deb6..0ab22f22c 100644 --- a/test/plugins/RewardableERC20.test.ts +++ b/test/plugins/RewardableERC20.test.ts @@ -18,12 +18,16 @@ 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 { rewardableVault: RewardableERC4626VaultTest | RewardableERC20WrapperTest rewardableAsset: ERC20MockRewarding rewardToken: ERC20MockDecimals + rewardableVaultFactory: ContractFactory } // 18 cases: test two wrappers with 2 combinations of decimals [6, 8, 18] @@ -76,6 +80,7 @@ for (const wrapperName of wrapperNames) { rewardableVault, rewardableAsset, rewardToken, + rewardableVaultFactory, } } return fixture @@ -118,11 +123,12 @@ for (const wrapperName of wrapperNames) { describe(wrapperName, () => { // Decimals let shareDecimals: number - + let rewardShareDecimals: number // Assets let rewardableVault: RewardableERC20WrapperTest | RewardableERC4626VaultTest let rewardableAsset: ERC20MockRewarding let rewardToken: ERC20MockDecimals + let rewardableVaultFactory: ContractFactory // Main let alice: Wallet @@ -141,14 +147,16 @@ 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) 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)) }) @@ -181,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 () => { @@ -192,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)) @@ -211,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) @@ -223,6 +237,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)) @@ -259,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 () => { @@ -267,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 () => { @@ -276,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) + ) }) }) @@ -303,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) ) @@ -314,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)) }) @@ -337,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) + ) }) }) @@ -378,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) + ) }) }) @@ -404,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 () => { @@ -413,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) + ) }) }) @@ -433,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 () => { @@ -445,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 () => { @@ -454,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) + ) }) }) @@ -480,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 () => { @@ -494,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 () => { @@ -511,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) }) }) @@ -540,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 () => { @@ -552,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 () => { @@ -565,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) + ) }) }) @@ -576,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() @@ -586,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 () => { @@ -598,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 () => { @@ -616,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) ) }) }) @@ -667,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 diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index 83c6bf2eb..926d33902 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/AaveV3FiatCollateral.test.ts b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts index 89bc877c6..a15ac37a2 100644 --- a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts @@ -15,6 +15,7 @@ import { noop } from 'lodash' import { PRICE_TIMEOUT } from '#/test/fixtures' import { resetFork } from './helpers' import { whileImpersonating } from '#/test/utils/impersonation' +import { pushOracleForward } from '../../../utils/oracles' import { forkNetwork, AUSDC_V3, @@ -72,6 +73,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()) @@ -211,6 +215,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-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap index 62ee74c7f..9f00d8e49 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 @@ -4,26 +4,26 @@ exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas 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`; +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`] = `87625`; -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`] = `87625`; -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`] = `89656`; +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`] = `88040`; diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index ed84d0b20..0ba9baf74 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 { @@ -22,15 +28,20 @@ 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' import { expectPrice, expectRTokenPrice, - expectUnpriced, setOraclePrice, + expectUnpriced, } from '../../../utils/oracles' import { advanceBlocks, @@ -204,7 +215,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ORACLE_ERROR, stkAave.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -228,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, @@ -423,7 +434,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( { @@ -432,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, @@ -440,6 +451,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_PRE_BUFFER, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: bn(0), + delayUntilDefault, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('defaultThreshold zero') }) }) @@ -653,10 +682,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() @@ -672,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, @@ -697,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, @@ -968,9 +1001,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 6cc30614b..13f8ae677 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -4,26 +4,26 @@ 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`] = `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`] = `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`] = `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`] = `92233`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92285`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92307`; +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 soft default 1`] = `127282`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91436`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91488`; diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index 79f063c58..e21a0f66a 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()) @@ -285,6 +289,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, + itChecksNonZeroDefaultThreshold: it, resetFork, collateralName: 'AnkrStakedETH', chainlinkDefaultAnswer, 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 513e5ca5b..64458dcd1 100644 --- a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -4,26 +4,26 @@ 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`] = `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/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index 9f659f1b4..8f5bb5efe 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 @@ -241,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 489f89d3d..fbc3f6874 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/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap index afb2111ab..f7366a3fb 100644 --- a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -4,26 +4,26 @@ 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`] = `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/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 40465f48d..e077f3567 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' @@ -56,6 +61,7 @@ export default function fn( itChecksTargetPerRefDefault, itChecksRefPerTokDefault, itChecksPriceChanges, + itChecksNonZeroDefaultThreshold, itHasRevenueHiding, itIsPricedByPeg, resetFork, @@ -105,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) }) @@ -286,28 +298,40 @@ 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('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) }) @@ -319,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 () => { @@ -361,29 +400,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) }) }) @@ -547,9 +584,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) }) itChecksRefPerTokDefault('after hard default', async () => { diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 876b6e5e5..aefae34d5 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 { @@ -22,7 +28,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' @@ -207,7 +218,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -230,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, @@ -425,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, @@ -461,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, @@ -469,6 +480,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_PRE_BUFFER, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: bn(0), + delayUntilDefault, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('defaultThreshold zero') }) }) @@ -666,10 +695,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() @@ -685,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, @@ -710,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, @@ -1054,9 +1087,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 b0874c79c..23638304f 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`] = `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`] = `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`] = `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`] = `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/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 45f9e0cc8..7c91bd206 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' @@ -119,6 +120,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()) @@ -353,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, @@ -361,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, @@ -369,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 }) } @@ -396,6 +409,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/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap index a899da3b8..d2dee358c 100644 --- a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -4,26 +4,26 @@ 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`] = `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`] = `132572`; -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`] = `126704`; -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/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index a9a939a02..3f9628fc7 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -8,11 +8,11 @@ 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' -import { expectUnpriced } from '../../../utils/oracles' +import { expectDecayedPrice, expectExactPrice, expectUnpriced } from '../../../utils/oracles' import { advanceBlocks, advanceTime, @@ -393,29 +393,49 @@ 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 Unpriced + // 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 () => { 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) @@ -425,28 +445,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) }) }) @@ -619,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) @@ -631,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) @@ -685,9 +711,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) }) it('after hard default', async () => { diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index 091863c24..fd62e8ee7 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,53 @@ 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) + + // Should decay after oracle timeout + await advanceTime(await collateral.oracleTimeout()) + await expectDecayedPrice(collateral.address) + + // Should be unpriced after price timeout + await advanceTime(await collateral.priceTimeout()) + await expectUnpriced(collateral.address) // refresh() should not revert await collateral.refresh() + }) - // Should be unpriced - await expectUnpriced(collateral.address) + 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() - // 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]) + // Stale should be false again + expect(await mockRTokenAsset.stale()).to.be.false }) } 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 ea0003577..4e3d02729 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,26 +4,26 @@ 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`] = `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`] = `79713`; -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`] = `79713`; -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 7c535da1b..acdeaf5b1 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`] = `485368`; -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`] = `480752`; -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`] = `594734`; -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`] = `589852`; -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`] = `478620`; -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`] = `474226`; -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`] = `544737`; -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`] = `536931`; -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`] = `713285`; -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`] = `713359`; -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`] = `701051`; -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`] = `693709`; 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 5f891c687..ffa84bf24 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,26 +4,26 @@ 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`] = `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/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index e1646193a..ab50ef36a 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,53 @@ 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) + + // Should decay after oracle timeout + await advanceTime(await collateral.oracleTimeout()) + await expectDecayedPrice(collateral.address) + + // Should be unpriced after price timeout + await advanceTime(await collateral.priceTimeout()) + await expectUnpriced(collateral.address) // refresh() should not revert await collateral.refresh() + }) - // Should be unpriced - await expectUnpriced(collateral.address) + 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() - // 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]) + // Stale should be false again + expect(await mockRTokenAsset.stale()).to.be.false }) } 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 7ccdd8462..7920079d2 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,26 +4,26 @@ 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`] = `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`] = `79713`; -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`] = `79713`; -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 d4975bf94..5470886e1 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`] = `485368`; -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`] = `480826`; -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`] = `594734`; -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`] = `589778`; -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`] = `478546`; -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`] = `474004`; -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`] = `544811`; -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`] = `536931`; -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`] = `713211`; -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`] = `713581`; -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`] = `701125`; -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`] = `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 20e9558ee..80285bbb1 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,26 +4,26 @@ 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`] = `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/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 7ee5c7dc0..f604c19eb 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 = () => Promise diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index 8d0a4fe8e..ea7c5554f 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -9,6 +9,7 @@ import { MockV3Aggregator__factory, TestICollateral, } from '../../../../typechain' +import { pushOracleForward } from '../../../utils/oracles' import { networkConfig } from '../../../../common/configuration' import { bn, fp } from '../../../../common/numbers' import { expect } from 'chai' @@ -128,6 +129,9 @@ all.forEach((curr: FTokenEnumeration) => { ) 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()) @@ -252,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/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index b11dbcda0..da1cb92e3 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`] = `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`] = `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`] = `115338`; +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`] = `115338`; +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`] = `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`] = `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`] = `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`] = `115530`; +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`] = `115530`; +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`] = `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`] = `149997`; -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`] = `123820`; +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`] = `123820`; +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`] = `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`] = `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`] = `142477`; -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`] = `118468`; +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`] = `118468`; +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`] = `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/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index d47e23919..7ac77c01d 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 {} // 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() @@ -210,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 }) } @@ -244,6 +259,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/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap index 30de100fa..58a27e831 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,14 +4,14 @@ 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`] = `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/LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts index 94593665b..1f4213ac6 100644 --- a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts @@ -12,6 +12,7 @@ import { TestICollateral, IWSTETH, } 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' @@ -91,6 +92,11 @@ export const deployCollateral = async ( opts.targetPerRefChainlinkTimeout, { gasLimit: 2000000000 } ) + + // Push forward chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + await pushOracleForward(opts.targetPerRefChainlinkFeed!) + 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 @@ -261,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/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap index dab42bfd7..7abbfa805 100644 --- a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -4,26 +4,26 @@ 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`] = `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/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index 6ecee4977..a1c99cfff 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -10,6 +10,7 @@ import { ethers } from 'hardhat' import collateralTests from '../collateralTests' import { getResetFork } from '../helpers' import { CollateralOpts } from '../pluginTestTypes' +import { pushOracleForward } from '../../../utils/oracles' import { DEFAULT_THRESHOLD, DELAY_UNTIL_DEFAULT, @@ -75,6 +76,9 @@ const makeAaveFiatCollateralTestSuite = ( ) await collateral.deployed() + // Push forward chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + await expect(collateral.refresh()) return collateral @@ -326,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 c745f7317..f9a0339f9 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -15,6 +15,7 @@ import { ethers } from 'hardhat' import collateralTests from '../collateralTests' import { getResetFork } from '../helpers' import { CollateralOpts } from '../pluginTestTypes' +import { pushOracleForward } from '../../../utils/oracles' import { DEFAULT_THRESHOLD, DELAY_UNTIL_DEFAULT, @@ -77,6 +78,10 @@ const makeAaveNonFiatCollateralTestSuite = ( )) as unknown as TestICollateral await collateral.deployed() + // Push forward chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + await pushOracleForward(opts.targetPrRefFeed!) + await expect(collateral.refresh()) return collateral @@ -230,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 16dd346ae..8e934cedc 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -15,6 +15,7 @@ import { ethers } from 'hardhat' import collateralTests from '../collateralTests' import { getResetFork } from '../helpers' import { CollateralOpts } from '../pluginTestTypes' +import { pushOracleForward } from '../../../utils/oracles' import { DELAY_UNTIL_DEFAULT, FORK_BLOCK, @@ -70,6 +71,9 @@ const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise 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/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap index d55a1ae73..049d64450 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,82 +4,82 @@ 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`] = `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`] = `172067`; -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`] = `172067`; -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 ERC20 transfer 1`] = `73881`; 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`] = `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`] = `172473`; -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`] = `172473`; -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 ERC20 transfer 1`] = `73881`; 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`] = `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`] = `170779`; -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`] = `170779`; -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 de69e7ba6..26e77e6a8 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,54 +4,54 @@ 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`] = `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`] = `192065`; -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`] = `192065`; -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 ERC20 transfer 1`] = `73881`; 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`] = `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`] = `231329`; -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`] = `231329`; -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 9148485da..a420cba2b 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,18 +4,18 @@ 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`] = `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/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index 11b73fbaa..34bfefbb2 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 7b2cd8e9a..d10488770 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -12,6 +12,7 @@ import { IReth, WETH9, } 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' @@ -89,6 +90,11 @@ export const deployCollateral = async (opts: RethCollateralOpts = {}): Promise { 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 f55d5a365..6b7479212 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(), @@ -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 [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 - ;[, lotHigh] = await rsrAsset.lotPrice() - const sellAmtRSR1 = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(lotHigh) + ;[low] = await rsrAsset.price() + const sellAmtRSR1 = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(low) const buyAmtBidRSR1 = toMinBuyAmt( sellAmtRSR1, rsrPrice, diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index a3ab63214..f1380b63f 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 6386b158f..38b11aba2 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 70e0fa263..c247b1cf9 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 8b1cfa00f..815ff2f7f 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 a2a67dd94..a9021be24 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, diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 89a9ca08e..3be1a0a11 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`] = `12082333`; -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`] = `9836935`; -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`] = `13617164`; +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`] = `20897690`; +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`] = `10991870`; +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`] = `8713158`; +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`] = `6561504`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6592449`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `15130053`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14823897`; diff --git a/test/utils/oracles.ts b/test/utils/oracles.ts index 30c290f24..2444878fe 100644 --- a/test/utils/oracles.ts +++ b/test/utils/oracles.ts @@ -8,6 +8,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 ( @@ -86,6 +93,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) diff --git a/test/utils/trades.ts b/test/utils/trades.ts index 433c9e453..b952deabf 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)