From 58e04980dd4b021b0070bc8946004b4dd03e9982 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Tue, 6 Feb 2024 21:00:20 -0500 Subject: [PATCH] Revert "3.1.0" (#1060) --- .github/workflows/tests.yml | 28 - .openzeppelin/base_8453.json | 184 --- .openzeppelin/mainnet.json | 200 +-- CHANGELOG.md | 102 +- README.md | 1 - common/configuration.ts | 10 +- common/numbers.ts | 4 +- contracts/facade/FacadeAct.sol | 6 +- contracts/facade/FacadeMonitor.sol | 211 --- contracts/facade/FacadeRead.sol | 31 +- contracts/facade/FacadeTest.sol | 4 +- contracts/interfaces/IAsset.sol | 10 +- contracts/interfaces/IAssetRegistry.sol | 2 +- contracts/interfaces/IBackingManager.sol | 37 - contracts/interfaces/IBasketHandler.sol | 2 - contracts/interfaces/IBroker.sol | 2 +- contracts/interfaces/IFacadeMonitor.sol | 49 - contracts/interfaces/IFacadeRead.sol | 13 +- contracts/interfaces/ITrade.sol | 3 - contracts/mixins/Versioned.sol | 2 +- contracts/p0/BackingManager.sol | 54 +- contracts/p0/BasketHandler.sol | 5 +- contracts/p0/Broker.sol | 13 +- contracts/p0/Distributor.sol | 22 +- contracts/p0/Furnace.sol | 12 +- contracts/p0/Main.sol | 3 +- contracts/p0/RevenueTrader.sol | 26 +- contracts/p0/StRSR.sol | 17 +- contracts/p0/mixins/TradingLib.sol | 77 +- contracts/p1/AssetRegistry.sol | 2 - contracts/p1/BackingManager.sol | 68 +- contracts/p1/BasketHandler.sol | 7 +- contracts/p1/Broker.sol | 33 +- contracts/p1/Distributor.sol | 31 +- contracts/p1/Furnace.sol | 12 +- contracts/p1/Main.sol | 3 +- contracts/p1/RToken.sol | 5 + contracts/p1/RevenueTrader.sol | 23 +- contracts/p1/StRSR.sol | 13 +- .../p1/mixins/RecollateralizationLib.sol | 189 ++- contracts/p1/mixins/TradeLib.sol | 2 +- contracts/p1/mixins/Trading.sol | 4 +- .../assets/AppreciatingFiatCollateral.sol | 2 +- contracts/plugins/assets/Asset.sol | 85 +- .../plugins/assets/EURFiatCollateral.sol | 2 - contracts/plugins/assets/FiatCollateral.sol | 6 +- contracts/plugins/assets/L2LSDCollateral.sol | 3 +- .../plugins/assets/NonFiatCollateral.sol | 2 - contracts/plugins/assets/RTokenAsset.sol | 78 +- contracts/plugins/assets/VersionedAsset.sol | 2 +- .../assets/aave-v3/AaveV3FiatCollateral.sol | 4 +- .../assets/aave/ATokenFiatCollateral.sol | 4 +- .../assets/ankr/AnkrStakedEthCollateral.sol | 1 - .../plugins/assets/cbeth/CBETHCollateral.sol | 1 - .../assets/cbeth/CBETHCollateralL2.sol | 1 - contracts/plugins/assets/cbeth/README.md | 1 - .../compoundv2/CTokenFiatCollateral.sol | 2 - .../compoundv2/CTokenNonFiatCollateral.sol | 1 - .../assets/compoundv2/CTokenWrapper.sol | 4 +- .../plugins/assets/compoundv2/ICToken.sol | 18 +- .../assets/compoundv3/CTokenV3Collateral.sol | 13 +- .../assets/compoundv3/CusdcV3Wrapper.sol | 9 +- .../assets/compoundv3/ICusdcV3Wrapper.sol | 2 +- .../assets/compoundv3/WrappedERC20.sol | 7 - .../compoundv3/vendor/CometExtInterface.sol | 8 - .../assets/curve/CurveStableCollateral.sol | 2 +- .../curve/CurveStableMetapoolCollateral.sol | 3 - .../CurveStableRTokenMetapoolCollateral.sol | 5 - .../assets/curve/crv/CurveGaugeWrapper.sol | 11 - .../curve/cvx/vendor/ConvexStakingWrapper.sol | 190 ++- .../plugins/assets/dsr/SDaiCollateral.sol | 1 - .../plugins/assets/erc20/RewardableERC20.sol | 44 +- .../assets/erc20/RewardableERC20Wrapper.sol | 4 - .../assets/erc20/RewardableERC4626Vault.sol | 4 +- contracts/plugins/assets/frax-eth/README.md | 2 +- .../assets/frax-eth/SFraxEthCollateral.sol | 14 +- .../assets/lido/LidoStakedEthCollateral.sol | 2 - .../MorphoAaveV2TokenisedDeposit.sol | 4 +- .../morpho-aave/MorphoFiatCollateral.sol | 1 - .../morpho-aave/MorphoNonFiatCollateral.sol | 13 +- .../morpho-aave/MorphoTokenisedDeposit.sol | 65 +- .../assets/rocket-eth/RethCollateral.sol | 1 - .../stargate/StargatePoolFiatCollateral.sol | 1 - .../yearnv2/YearnV2CurveFiatCollateral.sol | 41 +- contracts/plugins/governance/Governance.sol | 23 +- contracts/plugins/mocks/AssetMock.sol | 11 +- contracts/plugins/mocks/CTokenWrapperMock.sol | 4 +- contracts/plugins/mocks/ComptrollerMock.sol | 13 +- .../plugins/mocks/RevenueTraderBackComp.sol | 4 +- .../mocks/upgrades/FacadeMonitorV2.sol | 23 - contracts/plugins/trading/GnosisTrade.sol | 12 +- docs/collateral.md | 24 +- docs/deployed-addresses/1-FacadeMonitor.md | 7 - docs/deployed-addresses/8453-FacadeMonitor.md | 8 - docs/deployment.md | 2 +- docs/exhaustive-tests.md | 10 +- docs/monitoring.md | 35 - docs/pause-freeze-states.md | 73 - docs/recollateralization.md | 16 +- .../addresses/84531-RTKN-tmp-deployments.json | 19 - scripts/confirmation/1_confirm_assets.ts | 39 +- scripts/deploy.ts | 2 - .../phase1-common/1_deploy_libraries.ts | 14 +- .../phase1-common/3_deploy_rsrAsset.ts | 4 +- .../phase2-assets/1_deploy_assets.ts | 6 +- .../phase2-assets/2_deploy_collateral.ts | 280 +--- .../phase2-assets/assets/deploy_crv.ts | 4 +- .../phase2-assets/assets/deploy_cvx.ts | 4 +- .../collaterals/deploy_aave_v3_usdbc.ts | 4 +- .../collaterals/deploy_aave_v3_usdc.ts | 6 +- .../collaterals/deploy_cbeth_collateral.ts | 12 +- .../deploy_compound_v2_collateral.ts | 18 +- .../deploy_convex_rToken_metapool_plugin.ts | 14 +- .../deploy_convex_stable_metapool_plugin.ts | 13 +- .../deploy_convex_stable_plugin.ts | 15 +- .../deploy_convex_volatile_plugin.ts | 142 ++ .../deploy_ctokenv3_usdbc_collateral.ts | 6 +- .../deploy_ctokenv3_usdc_collateral.ts | 6 +- .../deploy_curve_rToken_metapool_plugin.ts | 9 +- .../deploy_curve_stable_metapool_plugin.ts | 8 +- .../collaterals/deploy_curve_stable_plugin.ts | 10 +- .../deploy_curve_volatile_plugin.ts | 142 ++ .../collaterals/deploy_dsr_sdai.ts | 6 +- .../deploy_flux_finance_collateral.ts | 10 +- .../deploy_lido_wsteth_collateral.ts | 6 +- .../deploy_morpho_aavev2_plugin.ts | 30 +- .../deploy_rocket_pool_reth_collateral.ts | 6 +- .../phase2-assets/collaterals/deploy_sfrax.ts | 4 +- .../deploy_stargate_usdc_collateral.ts | 19 +- .../deploy_stargate_usdt_collateral.ts | 4 +- .../collaterals/deploy_yearn_v2_curve_usdc.ts | 104 -- .../collaterals/deploy_yearn_v2_curve_usdp.ts | 104 -- scripts/deployment/utils.ts | 9 +- scripts/exhaustive-tests/run-1.sh | 4 +- scripts/verification/6_verify_collateral.ts | 34 +- .../verify_aave_v3_usdbc.ts | 4 +- .../collateral-plugins/verify_aave_v3_usdc.ts | 6 +- .../collateral-plugins/verify_cbeth.ts | 12 +- .../verify_convex_stable.ts | 22 +- .../verify_convex_stable_metapool.ts | 8 +- .../verify_convex_stable_rtoken_metapool.ts | 9 +- .../verify_convex_volatile.ts | 98 ++ .../collateral-plugins/verify_curve_stable.ts | 10 +- .../verify_curve_stable_metapool.ts | 8 +- .../verify_curve_stable_rtoken_metapool.ts | 9 +- .../verify_curve_volatile.ts | 98 ++ .../collateral-plugins/verify_cusdbcv3.ts | 6 +- .../collateral-plugins/verify_cusdcv3.ts | 6 +- .../collateral-plugins/verify_morpho.ts | 16 +- .../collateral-plugins/verify_reth.ts | 6 +- .../collateral-plugins/verify_sdai.ts | 2 +- .../collateral-plugins/verify_wsteth.ts | 6 +- .../verify_yearn_v2_curve_usdc.ts | 73 - scripts/verify_etherscan.ts | 2 - tasks/deployment/deploy-facade-monitor.ts | 107 -- tasks/index.ts | 1 - test/Broker.test.ts | 85 +- test/Facade.test.ts | 573 ++----- test/FacadeWrite.test.ts | 4 +- test/Furnace.test.ts | 49 +- test/Governance.test.ts | 172 +- test/Main.test.ts | 84 +- test/RTokenExtremes.test.ts | 5 +- test/Recollateralization.test.ts | 127 +- test/Revenues.test.ts | 815 +--------- test/ZZStRSR.test.ts | 21 +- test/__snapshots__/Broker.test.ts.snap | 16 +- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Furnace.test.ts.snap | 34 +- test/__snapshots__/Main.test.ts.snap | 12 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 18 +- test/__snapshots__/Revenues.test.ts.snap | 26 +- test/__snapshots__/ZZStRSR.test.ts.snap | 6 +- test/fixtures.ts | 61 +- test/integration/AssetPlugins.test.ts | 211 +-- test/integration/EasyAuction.test.ts | 7 +- .../__snapshots__/CTokenVaultGas.test.ts.snap | 25 + test/integration/fixtures.ts | 55 +- test/integration/fork-block-numbers.ts | 1 - test/monitor/FacadeMonitor.test.ts | 1417 ----------------- test/plugins/Asset.test.ts | 369 ++--- test/plugins/Collateral.test.ts | 591 ++----- test/plugins/RewardableERC20.test.ts | 280 +--- .../__snapshots__/Collateral.test.ts.snap | 8 +- .../aave-v3/AaveV3FiatCollateral.test.ts | 5 - .../AaveV3FiatCollateral.test.ts.snap | 24 +- .../aave/ATokenFiatCollateral.test.ts | 72 +- .../ATokenFiatCollateral.test.ts.snap | 24 +- .../ankr/AnkrEthCollateralTestSuite.test.ts | 5 - .../AnkrEthCollateralTestSuite.test.ts.snap | 24 +- .../cbeth/CBETHCollateral.test.ts | 6 - .../cbeth/CBETHCollateralL2.test.ts | 1 - .../CBETHCollateral.test.ts.snap | 24 +- .../individual-collateral/collateralTests.ts | 510 +----- .../compoundv2/CTokenFiatCollateral.test.ts | 70 +- .../CTokenFiatCollateral.test.ts.snap | 24 +- .../compoundv3/CometTestSuite.test.ts | 14 - .../compoundv3/CusdcV3Wrapper.test.ts | 71 +- .../__snapshots__/CometTestSuite.test.ts.snap | 24 +- .../curve/collateralTests.ts | 487 +----- .../CrvStableRTokenMetapoolTestSuite.test.ts | 51 +- .../CrvStableMetapoolSuite.test.ts.snap | 24 +- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 +- .../CrvStableTestSuite.test.ts.snap | 24 +- .../CvxStableRTokenMetapoolTestSuite.test.ts | 51 +- .../curve/cvx/CvxStableTestSuite.test.ts | 49 + .../CvxStableMetapoolSuite.test.ts.snap | 24 +- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 +- .../CvxStableTestSuite.test.ts.snap | 24 +- .../curve/cvx/helpers.ts | 40 +- .../dsr/SDaiCollateralTestSuite.test.ts | 8 +- .../SDaiCollateralTestSuite.test.ts.snap | 24 +- .../plugins/individual-collateral/fixtures.ts | 13 +- .../flux-finance/FTokenFiatCollateral.test.ts | 5 - .../FTokenFiatCollateral.test.ts.snap | 96 +- .../frax-eth/SFrxEthTestSuite.test.ts | 24 +- .../SFrxEthTestSuite.test.ts.snap | 12 +- .../frax/SFraxCollateralTestSuite.test.ts | 1 - .../lido/LidoStakedEthTestSuite.test.ts | 7 - .../LidoStakedEthTestSuite.test.ts.snap | 24 +- .../MorphoAAVEFiatCollateral.test.ts | 158 +- .../MorphoAAVENonFiatCollateral.test.ts | 58 +- ...orphoAAVESelfReferentialCollateral.test.ts | 7 +- .../MorphoAaveV2TokenisedDeposit.test.ts | 176 +- .../MorphoAAVEFiatCollateral.test.ts.snap | 84 +- .../MorphoAAVENonFiatCollateral.test.ts.snap | 48 +- ...AAVESelfReferentialCollateral.test.ts.snap | 16 +- .../individual-collateral/pluginTestTypes.ts | 3 - .../RethCollateralTestSuite.test.ts | 7 - .../RethCollateralTestSuite.test.ts.snap | 24 +- .../stargate/StargateUSDCTestSuite.test.ts | 1 - .../StargateETHTestSuite.test.ts.snap | 57 + .../StargateUSDCTestSuite.test.ts.snap | 29 - .../YearnV2CurveFiatCollateral.test.ts | 4 +- .../yearnv2/constants.ts | 2 - test/scenario/BadCollateralPlugin.test.ts | 4 +- test/scenario/ComplexBasket.test.ts | 34 +- test/scenario/MaxBasketSize.test.ts | 8 +- test/scenario/NestedRTokens.test.ts | 4 +- test/scenario/NontrivialPeg.test.ts | 6 +- test/scenario/RevenueHiding.test.ts | 4 +- test/scenario/SetProtocol.test.ts | 8 +- .../__snapshots__/MaxBasketSize.test.ts.snap | 18 +- test/utils/oracles.ts | 16 - test/utils/trades.ts | 25 +- 246 files changed, 2975 insertions(+), 8917 deletions(-) delete mode 100644 contracts/facade/FacadeMonitor.sol delete mode 100644 contracts/interfaces/IFacadeMonitor.sol delete mode 100644 contracts/plugins/mocks/upgrades/FacadeMonitorV2.sol delete mode 100644 docs/deployed-addresses/1-FacadeMonitor.md delete mode 100644 docs/deployed-addresses/8453-FacadeMonitor.md delete mode 100644 docs/monitoring.md delete mode 100644 docs/pause-freeze-states.md delete mode 100644 scripts/addresses/84531-RTKN-tmp-deployments.json create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts delete mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_yearn_v2_curve_usdc.ts delete mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_yearn_v2_curve_usdp.ts create mode 100644 scripts/verification/collateral-plugins/verify_convex_volatile.ts create mode 100644 scripts/verification/collateral-plugins/verify_curve_volatile.ts delete mode 100644 scripts/verification/collateral-plugins/verify_yearn_v2_curve_usdc.ts delete mode 100644 tasks/deployment/deploy-facade-monitor.ts create mode 100644 test/integration/__snapshots__/CTokenVaultGas.test.ts.snap delete mode 100644 test/monitor/FacadeMonitor.test.ts create mode 100644 test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap delete mode 100644 test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3bd5b1a460..7b553d57d7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -187,34 +187,6 @@ jobs: TS_NODE_SKIP_IGNORE: true MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} FORK_NETWORK: mainnet - - monitor-tests: - name: 'Monitor Tests (Mainnet)' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: 'yarn' - - run: yarn install --immutable - - name: 'Cache hardhat network fork' - uses: actions/cache@v3 - with: - path: cache/hardhat-network-fork - key: hardhat-network-fork-${{ runner.os }}-${{ hashFiles('test/integration/fork-block-numbers.ts') }} - restore-keys: | - hardhat-network-fork-${{ runner.os }}- - hardhat-network-fork- - - run: npx hardhat test ./test/monitor/*.test.ts - env: - NODE_OPTIONS: '--max-old-space-size=8192' - TS_NODE_SKIP_IGNORE: true - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} - FORK_NETWORK: mainnet - FORK: 1 - PROTO_IMPL: 1 - slither: name: 'Slither' runs-on: ubuntu-latest diff --git a/.openzeppelin/base_8453.json b/.openzeppelin/base_8453.json index 0d90ea97cb..6c4c4a3671 100644 --- a/.openzeppelin/base_8453.json +++ b/.openzeppelin/base_8453.json @@ -3144,190 +3144,6 @@ } } } - }, - "83264eb95f2f9ab0055f3cdf3d195b52003b35099a624ee29920f6a83be6b884": { - "address": "0xD45a441F334f6f27CDDA3728414FD26Cc5798E66", - "txHash": "0xcce3cfb75dad5e947efeab8a30cd981ca578d96f7a8bee1512a86b2849a0fa24", - "layout": { - "solcVersion": "0.8.19", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "ContextUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" - }, - { - "label": "_owner", - "offset": 0, - "slot": "51", - "type": "t_address", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" - }, - { - "label": "__gap", - "offset": 0, - "slot": "52", - "type": "t_array(t_uint256)49_storage", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" - }, - { - "label": "__gap", - "offset": 0, - "slot": "101", - "type": "t_array(t_uint256)50_storage", - "contract": "ERC1967UpgradeUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" - }, - { - "label": "__gap", - "offset": 0, - "slot": "151", - "type": "t_array(t_uint256)50_storage", - "contract": "UUPSUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_uint256)49_storage": { - "label": "uint256[49]", - "numberOfBytes": "1568" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - }, - "07b40b651527d3b3c3f0d1fb77a991853411f5b7fd564a45478bb03e177adcae": { - "address": "0x69c20aD99eb1054cd7Da2809572205186975dA17", - "txHash": "0x05c19fbc6774d5e85aadba888cc56e0764a104c1da7e3fa9f0774dfba8a46215", - "layout": { - "solcVersion": "0.8.19", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "ContextUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" - }, - { - "label": "_owner", - "offset": 0, - "slot": "51", - "type": "t_address", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" - }, - { - "label": "__gap", - "offset": 0, - "slot": "52", - "type": "t_array(t_uint256)49_storage", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" - }, - { - "label": "__gap", - "offset": 0, - "slot": "101", - "type": "t_array(t_uint256)50_storage", - "contract": "ERC1967UpgradeUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" - }, - { - "label": "__gap", - "offset": 0, - "slot": "151", - "type": "t_array(t_uint256)50_storage", - "contract": "UUPSUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_uint256)49_storage": { - "label": "uint256[49]", - "numberOfBytes": "1568" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } } } } diff --git a/.openzeppelin/mainnet.json b/.openzeppelin/mainnet.json index d0dae943ca..8ae5721830 100644 --- a/.openzeppelin/mainnet.json +++ b/.openzeppelin/mainnet.json @@ -3747,7 +3747,10 @@ }, "t_enum(TradeKind)25002": { "label": "enum TradeKind", - "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], + "members": [ + "DUTCH_AUCTION", + "BATCH_AUCTION" + ], "numberOfBytes": "1" }, "t_mapping(t_contract(IERC20)15191,t_contract(ITrade)27151)": { @@ -4040,7 +4043,11 @@ }, "t_enum(CollateralStatus)24460": { "label": "enum CollateralStatus", - "members": ["SOUND", "IFFY", "DISABLED"], + "members": [ + "SOUND", + "IFFY", + "DISABLED" + ], "numberOfBytes": "1" }, "t_mapping(t_bytes32,t_bytes32)": { @@ -6333,7 +6340,10 @@ }, "t_enum(TradeKind)17751": { "label": "enum TradeKind", - "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], + "members": [ + "DUTCH_AUCTION", + "BATCH_AUCTION" + ], "numberOfBytes": "1" }, "t_mapping(t_contract(IERC20)11113,t_contract(ITrade)19704)": { @@ -6642,190 +6652,6 @@ } } } - }, - "f0632c54f5763a16d6d87d14d0e7a80a079e8b998507fa1d081ee3b631c3961c": { - "address": "0xA42850A760151bb3ACF17E7f8643EB4d864bF7a6", - "txHash": "0xfa37e2544175813e2b4308c62f14f05f336a62ea25c94dd9346f710449498d0c", - "layout": { - "solcVersion": "0.8.19", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "ContextUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" - }, - { - "label": "_owner", - "offset": 0, - "slot": "51", - "type": "t_address", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" - }, - { - "label": "__gap", - "offset": 0, - "slot": "52", - "type": "t_array(t_uint256)49_storage", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" - }, - { - "label": "__gap", - "offset": 0, - "slot": "101", - "type": "t_array(t_uint256)50_storage", - "contract": "ERC1967UpgradeUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" - }, - { - "label": "__gap", - "offset": 0, - "slot": "151", - "type": "t_array(t_uint256)50_storage", - "contract": "UUPSUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_uint256)49_storage": { - "label": "uint256[49]", - "numberOfBytes": "1568" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } - }, - "ebc9c3f1c253e562c3d21649a4c7d904b40ed64689bc3d3bc57bbe09fcd1d120": { - "address": "0x35fDc5537c32588bfc97b393A8ed522Df737af5A", - "txHash": "0xc1d9400b9492c969e5a156fa8e419ccd8a1138160f6eb4079192455e3af357e6", - "layout": { - "solcVersion": "0.8.19", - "storage": [ - { - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "t_uint8", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", - "retypedFrom": "bool" - }, - { - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "t_bool", - "contract": "Initializable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" - }, - { - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)50_storage", - "contract": "ContextUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" - }, - { - "label": "_owner", - "offset": 0, - "slot": "51", - "type": "t_address", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" - }, - { - "label": "__gap", - "offset": 0, - "slot": "52", - "type": "t_array(t_uint256)49_storage", - "contract": "OwnableUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" - }, - { - "label": "__gap", - "offset": 0, - "slot": "101", - "type": "t_array(t_uint256)50_storage", - "contract": "ERC1967UpgradeUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" - }, - { - "label": "__gap", - "offset": 0, - "slot": "151", - "type": "t_array(t_uint256)50_storage", - "contract": "UUPSUpgradeable", - "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" - } - ], - "types": { - "t_address": { - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_uint256)49_storage": { - "label": "uint256[49]", - "numberOfBytes": "1568" - }, - "t_array(t_uint256)50_storage": { - "label": "uint256[50]", - "numberOfBytes": "1600" - }, - "t_bool": { - "label": "bool", - "numberOfBytes": "1" - }, - "t_uint256": { - "label": "uint256", - "numberOfBytes": "32" - }, - "t_uint8": { - "label": "uint8", - "numberOfBytes": "1" - } - } - } } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index a5330e5235..693d32bd91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,93 +1,5 @@ # Changelog -# 3.1.0 - -### Upgrade Steps -- Required - -Upgrade all core contracts and _all_ assets. Most ERC20s do not need to be upgraded. Use `Deployer.deployRTokenAsset()` to create a new `RTokenAsset` instance. This asset should be swapped too. - -ERC20s that _do_ need to be upgraded: - -- Morpho -- Convex -- CompoundV3 - -Then, call `Broker.cacheComponents()`. - -Finally, call `Broker.setBatchTradeImplementation(newGnosisTrade)`. - -### Core Protocol Contracts - -- `BackingManager` [+2 slots] - - Replace use of `lotPrice()` with `price()` everywhere - - Track `tokensOut` on trades and account for during collateralization math - - Call `StRSR.payoutRewards()` after forwarding RSR - - Make `backingBuffer` math precise - - Add caching in `RecollateralizationLibP1` - - Use `price().low` instead of `price().high` to compute maximum sell amounts -- `BasketHandler` - - Replace use of `lotPrice()` with `price()` everywhere - - Minor gas optimizations to status tracking and custom redemption math -- `Broker` [+1 slot] - - Cache `rToken` address and add `cacheComponents()` helper - - Allow `reportViolation()` to be called when paused or frozen - - Disallow starting dutch trades with non-RTokenAsset assets when `lastSave() != block.timestamp` -- `Distributor` - - Call `RevenueTrader.distributeTokenToBuy()` before distribution table changes - - Call `StRSR.payoutRewards()` or `Furnace.melt()` after distributions - - Minor gas optimizations -- `Furnace` - - Allow melting while frozen -- `Main` - - Remove `furnace.melt()` from `poke()` -- `RevenueTrader` - - Replace use of `lotPrice()` with `price()` everywhere - - Ensure `settleTrade` cannot be reverted due to `tokenToBuy` distribution - - Ensure during `manageTokens()` that the Distributor is configured for the `tokenToBuy` -- `StRSR` - - Use correct era in `UnstakingStarted` event - - Expose `draftEra` via `getDraftEra()` view - -### Facades - -- `FacadeMonitor` - - Add `batchAuctionsDisabled()` view - - Add `dutchAuctionsDisabled()` view - - Add `issuanceAvailable()` view - - Add `redemptionAvailable()` view - - Add `backingRedeemable()` view -- `FacadeRead` - - Add `draftEra` argument to `pendingUnstakings()` - - Remove `.melt()` calls during pokes - -## Plugins - -### Assets - -- ALL - - Deprecate `lotPrice()` - - Alter `price().low` to decay downwards to 0 over the price timeout - - Alter `price().high` to decay upwards to 3x over the price timeout - - Move `ORACLE_TIMEOUT_BUFFER` into code, as opposed to incorporating at the deployment script level - - Make`refPerTok()` smoother during event of hard default - - Check for `defaultThreshold > 0` in constructors - - Add 9 more decimals of precision to reward accounting (some wrappers excluded) -- compoundv2: make wrapper much more gas efficient during COMP claim -- compoundv3 bugfix: check permission correctly on underlying comet -- curve: Also `refresh()` the RToken's AssetRegistry during `refresh()` -- convex: Update to latest approved wrapper from Convex team -- morpho-aave: Add ability to track and handout MORPHO rewards -- yearnv2: Use pricePerShare helper for more precision - -### Governance - -- Add a minimum voting delay of 1 day - -### Trading - -- `GnosisTrade` - - Add `sellAmount() returns (uint192)` view - # 3.0.1 ### Upgrade steps @@ -96,8 +8,6 @@ Update `BackingManager`, both `RevenueTraders` (rTokenTrader/rsrTrader), and cal # 3.0.0 -Bump solidity version to 0.8.19 - ### Upgrade Steps #### Required Steps @@ -128,7 +38,9 @@ It is acceptable to leave these function calls out of the initial upgrade tx and ### Core Protocol Contracts - `AssetRegistry` [+1 slot] - Summary: Other component contracts need to know when refresh() was last called + Summary: StRSR contract need to know when refresh() was last called + - # Add last refresh timestamp tracking and expose via `lastRefresh()` getter + 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 @@ -268,10 +180,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 e8ab78faec..1c569eb492 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ 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 3692d8068f..308bfc671e 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -109,7 +109,6 @@ interface INetworkConfig { AAVE_INCENTIVES?: string AAVE_EMISSIONS_MGR?: string AAVE_RESERVE_TREASURY?: string - AAVE_DATA_PROVIDER?: string COMPTROLLER?: string FLUX_FINANCE_COMPTROLLER?: string GNOSIS_EASY_AUCTION?: string @@ -225,7 +224,6 @@ export const networkConfig: { [key: string]: INetworkConfig } = { AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', - AAVE_DATA_PROVIDER: '0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d', FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', @@ -331,7 +329,6 @@ export const networkConfig: { [key: string]: INetworkConfig } = { }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', - AAVE_DATA_PROVIDER: '0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d', FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', @@ -431,7 +428,6 @@ export const networkConfig: { [key: string]: INetworkConfig } = { }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', - AAVE_DATA_PROVIDER: '0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d', FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', @@ -646,10 +642,6 @@ export interface IRTokenConfig { params: IConfig } -export interface IMonitorParams { - AAVE_V2_DATA_PROVIDER_ADDR: string -} - export interface IBackupInfo { backupUnit: string diversityFactor: BigNumber @@ -698,7 +690,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).sub(300) +export const MAX_ORACLE_TIMEOUT = BigNumber.from(2).pow(48).sub(1) export const MAX_TRADING_DELAY = 31536000 // 1 year export const MIN_WARMUP_PERIOD = 60 // 1 minute export const MAX_WARMUP_PERIOD = 31536000 // 1 year diff --git a/common/numbers.ts b/common/numbers.ts index d49a2a6606..6d53f464d1 100644 --- a/common/numbers.ts +++ b/common/numbers.ts @@ -16,9 +16,7 @@ export const pow10 = (exponent: BigNumberish): BigNumber => { // Convert `x` to a new BigNumber with decimals = `decimals`. // Input should have SCALE_DECIMALS (18) decimal places, and `decimals` should be less than 18. export const toBNDecimals = (x: BigNumberish, decimals: number): BigNumber => { - return decimals < SCALE_DECIMALS - ? BigNumber.from(x).div(pow10(SCALE_DECIMALS - decimals)) - : BigNumber.from(x).mul(pow10(decimals - SCALE_DECIMALS)) + return BigNumber.from(x).div(pow10(SCALE_DECIMALS - decimals)) } // Convert to the BigNumber representing a Fix from a BigNumberish. diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 45f32b4f7c..3534a1fa62 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -117,11 +117,11 @@ contract FacadeAct is IFacadeAct, Multicall { } surpluses[i] = erc20s[i].balanceOf(address(revenueTrader)); - (uint192 low, ) = reg.assets[i].price(); // {UoA/tok} - if (low == 0) continue; + (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} + if (lotLow == 0) continue; // {qTok} = {UoA} / {UoA/tok} - minTradeAmounts[i] = minTradeVolume.safeDiv(low, FLOOR).shiftl_toUint( + minTradeAmounts[i] = minTradeVolume.safeDiv(lotLow, FLOOR).shiftl_toUint( int8(reg.assets[i].erc20Decimals()) ); diff --git a/contracts/facade/FacadeMonitor.sol b/contracts/facade/FacadeMonitor.sol deleted file mode 100644 index e8221a1195..0000000000 --- a/contracts/facade/FacadeMonitor.sol +++ /dev/null @@ -1,211 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "../interfaces/IFacadeMonitor.sol"; -import "../interfaces/IRToken.sol"; -import "../libraries/Fixed.sol"; -import "../p1/RToken.sol"; -import "../plugins/assets/compoundv2/CTokenWrapper.sol"; -import "../plugins/assets/compoundv3/ICusdcV3Wrapper.sol"; -import "../plugins/assets/stargate/StargateRewardableWrapper.sol"; -import { StaticATokenV3LM } from "../plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol"; -import "../plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol"; - -interface IAaveProtocolDataProvider { - function getReserveData(address asset) - external - view - returns ( - uint256 availableLiquidity, - uint256 totalStableDebt, - uint256 totalVariableDebt, - uint256 liquidityRate, - uint256 variableBorrowRate, - uint256 stableBorrowRate, - uint256 averageStableBorrowRate, - uint256 liquidityIndex, - uint256 variableBorrowIndex, - uint40 lastUpdateTimestamp - ); -} - -interface IStaticATokenLM is IERC20 { - // solhint-disable-next-line func-name-mixedcase - function UNDERLYING_ASSET_ADDRESS() external view returns (address); - - function dynamicBalanceOf(address account) external view returns (uint256); -} - -/** - * @title FacadeMonitor - * @notice A UX-friendly layer for monitoring RTokens - */ -contract FacadeMonitor is Initializable, OwnableUpgradeable, UUPSUpgradeable, IFacadeMonitor { - using FixLib for uint192; - - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - // solhint-disable-next-line var-name-mixedcase - address public immutable AAVE_V2_DATA_PROVIDER; - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(MonitorParams memory params) { - AAVE_V2_DATA_PROVIDER = params.AAVE_V2_DATA_PROVIDER_ADDR; - _disableInitializers(); - } - - function init(address initialOwner) public initializer { - require(initialOwner != address(0), "invalid owner address"); - - __Ownable_init(); - __UUPSUpgradeable_init(); - _transferOwnership(initialOwner); - } - - // === Views === - - /// @return Whether batch auctions are disabled for a specific rToken - function batchAuctionsDisabled(IRToken rToken) external view returns (bool) { - return rToken.main().broker().batchTradeDisabled(); - } - - /// @return Whether any dutch auction is disabled for a specific rToken - function dutchAuctionsDisabled(IRToken rToken) external view returns (bool) { - bool disabled = false; - - IERC20[] memory erc20s = rToken.main().assetRegistry().erc20s(); - for (uint256 i = 0; i < erc20s.length; ++i) { - if (rToken.main().broker().dutchTradeDisabled(IERC20Metadata(address(erc20s[i])))) - disabled = true; - } - - return disabled; - } - - /// @return Which percentage of issuance throttle is still available for a specific rToken - function issuanceAvailable(IRToken rToken) external view returns (uint256) { - ThrottleLib.Params memory params = RTokenP1(address(rToken)).issuanceThrottleParams(); - - // Calculate hourly limit as: max(params.amtRate, supply.mul(params.pctRate)) - uint256 limit = (rToken.totalSupply() * params.pctRate) / FIX_ONE_256; // {qRTok} - if (params.amtRate > limit) limit = params.amtRate; - - uint256 issueAvailable = rToken.issuanceAvailable(); - if (issueAvailable >= limit) return FIX_ONE_256; - - return (issueAvailable * FIX_ONE_256) / limit; - } - - function redemptionAvailable(IRToken rToken) external view returns (uint256) { - ThrottleLib.Params memory params = RTokenP1(address(rToken)).redemptionThrottleParams(); - - uint256 supply = rToken.totalSupply(); - - if (supply == 0) return FIX_ONE_256; - - // Calculate hourly limit as: max(params.amtRate, supply.mul(params.pctRate)) - uint256 limit = (supply * params.pctRate) / FIX_ONE_256; // {qRTok} - if (params.amtRate > limit) limit = supply < params.amtRate ? supply : params.amtRate; - - uint256 redeemAvailable = rToken.redemptionAvailable(); - if (redeemAvailable >= limit) return FIX_ONE_256; - - return (redeemAvailable * FIX_ONE_256) / limit; - } - - function backingReedemable( - IRToken rToken, - CollPluginType collType, - IERC20 erc20 - ) external view returns (uint256) { - uint256 backingBalance; - uint256 availableLiquidity; - - if (collType == CollPluginType.AAVE_V2 || collType == CollPluginType.MORPHO_AAVE_V2) { - address underlying; - if (collType == CollPluginType.AAVE_V2) { - // AAVE V2 - Uses Static wrapper - IStaticATokenLM staticAToken = IStaticATokenLM(address(erc20)); - backingBalance = staticAToken.dynamicBalanceOf( - address(rToken.main().backingManager()) - ); - underlying = staticAToken.UNDERLYING_ASSET_ADDRESS(); - } else { - // MORPHO AAVE V2 - MorphoAaveV2TokenisedDeposit mrpTknDeposit = MorphoAaveV2TokenisedDeposit( - address(erc20) - ); - backingBalance = mrpTknDeposit.convertToAssets( - mrpTknDeposit.balanceOf(address(rToken.main().backingManager())) - ); - underlying = mrpTknDeposit.underlying(); - } - - (availableLiquidity, , , , , , , , , ) = IAaveProtocolDataProvider( - AAVE_V2_DATA_PROVIDER - ).getReserveData(underlying); - } else if (collType == CollPluginType.AAVE_V3) { - StaticATokenV3LM staticAToken = StaticATokenV3LM(address(erc20)); - IERC20 aToken = staticAToken.aToken(); - IERC20 underlying = IERC20(staticAToken.asset()); - - backingBalance = staticAToken.convertToAssets( - staticAToken.balanceOf(address(rToken.main().backingManager())) - ); - availableLiquidity = underlying.balanceOf(address(aToken)); - } else if (collType == CollPluginType.COMPOUND_V2 || collType == CollPluginType.FLUX) { - ICToken cToken; - uint256 cTokenBal; - if (collType == CollPluginType.COMPOUND_V2) { - // CompoundV2 uses a vault to wrap the CToken - CTokenWrapper cTokenVault = CTokenWrapper(address(erc20)); - cToken = ICToken(address(cTokenVault.underlying())); - cTokenBal = cTokenVault.balanceOf(address(rToken.main().backingManager())); - } else { - // FLUX - Uses FToken directly (fork of CToken) - cToken = ICToken(address(erc20)); - cTokenBal = cToken.balanceOf(address(rToken.main().backingManager())); - } - IERC20 underlying = IERC20(cToken.underlying()); - - uint256 exchangeRate = cToken.exchangeRateStored(); - - backingBalance = (cTokenBal * exchangeRate) / 1e18; - availableLiquidity = underlying.balanceOf(address(cToken)); - } else if (collType == CollPluginType.COMPOUND_V3) { - ICusdcV3Wrapper cTokenV3Wrapper = ICusdcV3Wrapper(address(erc20)); - CometInterface cTokenV3 = CometInterface(address(cTokenV3Wrapper.underlyingComet())); - IERC20 underlying = IERC20(cTokenV3.baseToken()); - - backingBalance = cTokenV3Wrapper.underlyingBalanceOf( - address(rToken.main().backingManager()) - ); - availableLiquidity = underlying.balanceOf(address(cTokenV3)); - } else if (collType == CollPluginType.STARGATE) { - StargateRewardableWrapper stgWrapper = StargateRewardableWrapper(address(erc20)); - IStargatePool stgPool = stgWrapper.pool(); - - uint256 wstgBal = stgWrapper.balanceOf(address(rToken.main().backingManager())); - - backingBalance = stgPool.amountLPtoLD(wstgBal); - availableLiquidity = stgPool.totalLiquidity(); - } - - if (availableLiquidity == 0) { - return 0; // Avoid division by zero - } - - if (availableLiquidity >= backingBalance) { - return FIX_ONE_256; - } - - // Calculate the percentage - return (availableLiquidity * FIX_ONE_256) / backingBalance; - } - - // solhint-disable-next-line no-empty-blocks - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} -} diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 2e2ce936e0..eed25706aa 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -34,6 +34,7 @@ contract FacadeRead is IFacadeRead { // Poke Main main.assetRegistry().refresh(); + main.furnace().melt(); // {BU} BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(account); @@ -74,6 +75,7 @@ contract FacadeRead is IFacadeRead { // Poke Main reg.refresh(); + main.furnace().melt(); // Compute # of baskets to create `amount` qRTok uint192 baskets = (rTok.totalSupply() > 0) // {BU} @@ -119,6 +121,7 @@ contract FacadeRead is IFacadeRead { // Poke Main main.assetRegistry().refresh(); + main.furnace().melt(); uint256 supply = rTok.totalSupply(); @@ -200,7 +203,7 @@ contract FacadeRead is IFacadeRead { IBasketHandler basketHandler = rToken.main().basketHandler(); // solhint-disable-next-line no-empty-blocks - try rToken.main().furnace().melt() {} catch {} // <3.1.0 RTokens may revert while frozen + try rToken.main().furnace().melt() {} catch {} (erc20s, deposits) = basketHandler.quote(FIX_ONE, CEIL); @@ -239,6 +242,7 @@ contract FacadeRead is IFacadeRead { { IMain main = rToken.main(); main.assetRegistry().refresh(); + main.furnace().melt(); erc20s = main.assetRegistry().erc20s(); balances = new uint256[](erc20s.length); @@ -266,26 +270,25 @@ contract FacadeRead is IFacadeRead { // === Views === - /// @param draftEra {draftEra} The draft era to query unstakings for /// @param account The account for the query - /// @dev Use stRSR.draftRate() to convert {qDrafts} to {qRSR} - /// @return unstakings {qDrafts} All the pending StRSR unstakings for an account, in drafts - function pendingUnstakings( - RTokenP1 rToken, - uint256 draftEra, - address account - ) external view returns (Pending[] memory unstakings) { - StRSRP1 stRSR = StRSRP1(address(rToken.main().stRSR())); - uint256 left = stRSR.firstRemainingDraft(draftEra, account); - uint256 right = stRSR.draftQueueLen(draftEra, account); + /// @return unstakings All the pending StRSR unstakings for an account + function pendingUnstakings(RTokenP1 rToken, address account) + external + view + returns (Pending[] memory unstakings) + { + StRSRP1Votes stRSR = StRSRP1Votes(address(rToken.main().stRSR())); + uint256 era = stRSR.currentEra(); + uint256 left = stRSR.firstRemainingDraft(era, account); + uint256 right = stRSR.draftQueueLen(era, account); unstakings = new Pending[](right - left); for (uint256 i = 0; i < right - left; i++) { - (uint192 drafts, uint64 availableAt) = stRSR.draftQueues(draftEra, account, i + left); + (uint192 drafts, uint64 availableAt) = stRSR.draftQueues(era, account, i + left); uint192 diff = drafts; if (i + left > 0) { - (uint192 prevDrafts, ) = stRSR.draftQueues(draftEra, account, i + left - 1); + (uint192 prevDrafts, ) = stRSR.draftQueues(era, account, i + left - 1); diff = drafts - prevDrafts; } diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index f95d350282..512457c1b2 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -67,7 +67,6 @@ 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( @@ -76,7 +75,6 @@ contract FacadeTest is IFacadeTest { erc20s ); try main.rTokenTrader().manageTokens(rTokenERC20s, rTokenKinds) {} catch {} - try main.rTokenTrader().distributeTokenToBuy() {} catch {} // solhint-enable no-empty-blocks } @@ -100,6 +98,7 @@ contract FacadeTest is IFacadeTest { // Poke Main reg.refresh(); + main.furnace().melt(); address backingManager = address(main.backingManager()); IERC20 rsr = main.rsr(); @@ -136,7 +135,6 @@ 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 8126aa12ce..bd796190a7 100644 --- a/contracts/interfaces/IAsset.sol +++ b/contracts/interfaces/IAsset.sol @@ -27,14 +27,12 @@ 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 - /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility /// @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); @@ -69,14 +67,8 @@ 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 price() should decay over, after stale price + /// @return {s} Seconds that the lotPrice 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 add18d69b5..caeaac2f3e 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:refresher + /// @custom:interaction function refresh() external; /// Register `asset` diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index b9b3c5beca..0699da6d6c 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -2,38 +2,10 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "./IAssetRegistry.sol"; -import "./IBasketHandler.sol"; import "./IBroker.sol"; import "./IComponent.sol"; -import "./IRToken.sol"; -import "./IStRSR.sol"; import "./ITrading.sol"; -/// Memory struct for RecollateralizationLibP1 + RTokenAsset -/// Struct purposes: -/// 1. Configure trading -/// 2. Stay under stack limit with fewer vars -/// 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 - // basketsHeld.bottom is the number of full basket units held - - // Components - IBasketHandler bh; - IAssetRegistry ar; - IStRSR stRSR; - IERC20 rsr; - IRToken rToken; - // Gov Vars - uint192 minTradeVolume; // {UoA} - uint192 maxTradeSlippage; // {1} - // Cached values - uint192[] quantities; // {tok/BU} basket quantities - uint192[] bals; // {tok} balances in BackingManager + out on trades -} - /** * @title IBackingManager * @notice The BackingManager handles changes in the ERC20 balances that back an RToken. @@ -76,15 +48,6 @@ interface IBackingManager is IComponent, ITrading { /// @param erc20s The tokens to forward /// @custom:interaction RCEI function forwardRevenue(IERC20[] calldata erc20s) external; - - /// Structs for trading - /// @param basketsHeld The number of baskets held by the BackingManager - /// @return ctx The TradingContext - /// @return reg Contents of AssetRegistry.getRegistry() - function tradingContext(BasketRange memory basketsHeld) - external - view - returns (TradingContext memory ctx, Registry memory reg); } interface TestIBackingManager is IBackingManager, TestITrading { diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 2ed829d1b9..42bb8bf092 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -133,14 +133,12 @@ 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 - /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility /// @return lotLow {UoA/tok} The lower end of the lot price estimate /// @return lotHigh {UoA/tok} The upper end of the lot price estimate function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index fcaeac2c10..20e2ed0cb0 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -11,7 +11,7 @@ enum TradeKind { BATCH_AUCTION } -/// Cache of all prices for a pair to prevent re-lookup +/// Cache of all (lot) prices for a pair to prevent re-lookup struct TradePrices { uint192 sellLow; // {UoA/sellTok} can be 0 uint192 sellHigh; // {UoA/sellTok} should not be 0 diff --git a/contracts/interfaces/IFacadeMonitor.sol b/contracts/interfaces/IFacadeMonitor.sol deleted file mode 100644 index 6c4f6f8d2d..0000000000 --- a/contracts/interfaces/IFacadeMonitor.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -import "./IRToken.sol"; - -/** - * @title IFacadeMonitor - * @notice A monitoring layer for RTokens - */ - -/// PluginType -enum CollPluginType { - AAVE_V2, - AAVE_V3, - COMPOUND_V2, - COMPOUND_V3, - STARGATE, - FLUX, - MORPHO_AAVE_V2 -} - -/** - * @title MonitorParams - * @notice The set of protocol params needed for the required calculations - * Should be defined at deployment based on network - */ - -// solhint-disable var-name-mixedcase -struct MonitorParams { - // === AAVE_V2=== - address AAVE_V2_DATA_PROVIDER_ADDR; -} - -interface IFacadeMonitor { - // === Views === - function batchAuctionsDisabled(IRToken rToken) external view returns (bool); - - function dutchAuctionsDisabled(IRToken rToken) external view returns (bool); - - function issuanceAvailable(IRToken rToken) external view returns (uint256); - - function redemptionAvailable(IRToken rToken) external view returns (uint256); - - function backingReedemable( - IRToken rToken, - CollPluginType collType, - IERC20 erc20 - ) external view returns (uint256); -} diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index df5f039d64..44af758dec 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -85,15 +85,12 @@ interface IFacadeRead { uint256 amount; } - /// @param draftEra {draftEra} The draft era to query unstakings for /// @param account The account for the query - /// @dev Use stRSR.draftRate() to convert {qDrafts} to {qRSR} - /// @return {qDrafts} All the pending unstakings for an account, in drafts - function pendingUnstakings( - RTokenP1 rToken, - uint256 draftEra, - address account - ) external view returns (Pending[] memory); + /// @return All the pending StRSR unstakings for an account + function pendingUnstakings(RTokenP1 rToken, address account) + external + view + returns (Pending[] memory); /// Returns the prime basket /// @dev Indices are shared across return values diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index f9e95114f9..d05e3028f6 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -27,9 +27,6 @@ interface ITrade { function buy() external view returns (IERC20Metadata); - /// @return {tok} The sell amount of the trade, in whole tokens - function sellAmount() external view returns (uint192); - /// @return The timestamp at which the trade is projected to become settle-able function endTime() external view returns (uint48); diff --git a/contracts/mixins/Versioned.sol b/contracts/mixins/Versioned.sol index c70c7a8857..7518551125 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.1.0"; +string constant VERSION = "3.0.1"; /** * @title Versioned diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 34a28ce66a..c22df732c7 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -32,8 +32,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { mapping(TradeKind => uint48) private tradeEnd; // {s} last endTime() of an auction per kind - mapping(IERC20 => uint192) private tokensOut; // {tok} token balances out in ITrades - constructor() { ONE_BLOCK = NetworkConfigLib.blocktime(); } @@ -71,7 +69,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { returns (ITrade trade) { trade = super.settleTrade(sell); - delete tokensOut[trade.sell()]; // if the settler is the trade contract itself, try chaining with another rebalance() if (_msgSender() == address(trade)) { @@ -89,6 +86,7 @@ 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( @@ -137,8 +135,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { // Execute Trade ITrade trade = tryTrade(kind, req, prices); - tradeEnd[kind] = trade.endTime(); // {s} - tokensOut[trade.sell()] = trade.sellAmount(); // {tok} + tradeEnd[kind] = trade.endTime(); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); @@ -152,6 +149,7 @@ 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"); @@ -168,19 +166,15 @@ 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 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} + if (basketsHeld.bottom.gt(needed)) { + main.rToken().mint(basketsHeld.bottom.minus(needed)); + needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // keep buffer + } // Handout excess assets above what is needed, including any newly minted RToken RevenueTotals memory totals = main.distributor().totals(); @@ -209,40 +203,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } } - // === View === - - /// Structs for trading - /// @param basketsHeld The number of baskets held by the BackingManager - /// @return ctx The TradingContext - /// @return reg Contents of AssetRegistry.getRegistry() - function tradingContext(BasketRange memory basketsHeld) - public - view - returns (TradingContext memory ctx, Registry memory reg) - { - reg = main.assetRegistry().getRegistry(); - - ctx.basketsHeld = basketsHeld; - ctx.bh = main.basketHandler(); - ctx.ar = main.assetRegistry(); - ctx.stRSR = main.stRSR(); - ctx.rsr = main.rsr(); - ctx.rToken = main.rToken(); - ctx.minTradeVolume = minTradeVolume; - ctx.maxTradeSlippage = maxTradeSlippage; - ctx.quantities = new uint192[](reg.erc20s.length); - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - ctx.quantities[i] = ctx.bh.quantity(reg.erc20s[i]); - } - ctx.bals = new uint192[](reg.erc20s.length); - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - ctx.bals[i] = reg.assets[i].bal(address(this)) + tokensOut[reg.erc20s[i]]; - - // include StRSR's balance for RSR - if (reg.erc20s[i] == ctx.rsr) ctx.bals[i] += reg.assets[i].bal(address(ctx.stRSR)); - } - } - // === Private === /// Compromise on how many baskets are needed in order to recollateralize-by-accounting diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 998c25e65f..357b0a7251 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -183,8 +183,6 @@ 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 @@ -201,7 +199,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { require( main.hasRole(OWNER, _msgSender()) || - (lastStatus == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), + (status() == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), "basket unrefreshable" ); _switchBasket(); @@ -379,7 +377,6 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { /// Should not revert /// lowLow should be nonzero when the asset might be worth selling - /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility /// @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) diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index b53c2e0417..02b88de2f3 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 { + function reportViolation() external notTradingPausedOrFrozen { require(trades[_msgSender()], "unrecognized trade contract"); ITrade trade = ITrade(_msgSender()); TradeKind kind = trade.KIND(); @@ -239,11 +239,6 @@ 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; @@ -256,10 +251,4 @@ 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 264d7bfe7e..d305e9b521 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -33,11 +33,6 @@ 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); @@ -63,15 +58,13 @@ contract DistributorP0 is ComponentP0, IDistributor { { RevenueTotals memory revTotals = totals(); uint256 totalShares = isRSR ? revTotals.rsrTotal : revTotals.rTokenTotal; - if (totalShares > 0) tokensPerShare = amount / totalShares; - require(tokensPerShare > 0, "nothing to distribute"); + require(totalShares > 0, "nothing to distribute"); + tokensPerShare = amount / totalShares; } // 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); @@ -83,23 +76,12 @@ 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 ea0a404a2e..aa99a8140c 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 { + function melt() public notFrozen { if (uint48(block.timestamp) < uint64(lastPayout) + PERIOD) return; // # of whole periods that have passed since lastPayout @@ -58,9 +58,15 @@ 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 1859ad8ecb..9493b72c5c 100644 --- a/contracts/p0/Main.sol +++ b/contracts/p0/Main.sol @@ -37,7 +37,8 @@ contract MainP0 is Versioned, Initializable, Auth, ComponentRegistry, IMain { /// @custom:refresher function poke() external { - assetRegistry.refresh(); // runs furnace.melt() + assetRegistry.refresh(); + if (!frozen()) furnace.melt(); stRSR.payoutRewards(); // NOT basketHandler.refreshBasket } diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 8fecf2eb76..a62cf8bd18 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) returns (ITrade trade) { + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP0) + notTradingPausedOrFrozen + returns (ITrade trade) + { trade = super.settleTrade(sell); - - // solhint-disable-next-line no-empty-blocks - try this.distributeTokenToBuy() {} catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - } + _distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -80,18 +80,10 @@ 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.price(); // {UoA/tok} + (uint192 buyLow, uint192 buyHigh) = assetToBuy.lotPrice(); // {UoA/tok} require(buyHigh > 0 && buyHigh < FIX_MAX, "buy asset price unknown"); // For each ERC20: start auction of given kind @@ -107,7 +99,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { require(address(trades[erc20]) == address(0), "trade open"); require(erc20.balanceOf(address(this)) > 0, "0 balance"); - (uint192 sellLow, uint192 sellHigh) = assetToSell.price(); // {UoA/tok} + (uint192 sellLow, uint192 sellHigh) = assetToSell.lotPrice(); // {UoA/tok} TradingLibP0.TradeInfo memory trade = TradingLibP0.TradeInfo({ sell: assetToSell, diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index a9a3e597ec..fe3676dcef 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -77,11 +77,9 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { // {qRSR} How much reward RSR was held the last time rewards were paid out uint256 internal rsrRewardsAtLastPayout; - // Eras. These are only really here for equivalence with P1, which requires it - // If there's ever a total RSR wipeout to balances, this is incremented + // Era. If ever there's a total RSR wipeout, this is incremented + // This is only really here for equivalence with P1, which requires it uint256 internal era; - // If there's ever a total RSR wipeout to pending withdrawals, this is incremented - uint256 internal draftEra; // The momentary stake/unstake rate is rsrBacking/totalStaked {RSR/stRSR} // That rate is locked in when slow unstaking *begins* @@ -138,7 +136,6 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { setRewardRatio(rewardRatio_); setWithdrawalLeak(withdrawalLeak_); era = 1; - draftEra = 1; } /// Assign reward payouts to the staker pool @@ -204,7 +201,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { uint256 lastAvailableAt = index > 0 ? withdrawals[account][index - 1].availableAt : 0; uint256 availableAt = Math.max(block.timestamp + unstakingDelay, lastAvailableAt); withdrawals[account].push(Withdrawal(account, rsrAmount, stakeAmount, availableAt)); - emit UnstakingStarted(index, draftEra, account, rsrAmount, stakeAmount, availableAt); + emit UnstakingStarted(index, era, account, rsrAmount, stakeAmount, availableAt); } /// Complete delayed staking for an account, up to but not including draft ID `endId` @@ -242,7 +239,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { require(bh.isReady(), "basket not ready"); // Execute accumulated withdrawals - emit UnstakingCompleted(start, i, draftEra, account, total); + emit UnstakingCompleted(start, i, era, account, total); main.rsr().safeTransfer(account, total); } @@ -283,7 +280,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { } // Execute accumulated withdrawals - emit UnstakingCancelled(start, i, draftEra, account, total); + emit UnstakingCancelled(start, i, era, account, total); uint256 stakeAmount = total; if (totalStaked > 0) stakeAmount = (total * totalStaked) / rsrBacking; @@ -338,7 +335,6 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { uint256 withdrawalRSRtoTake = (rsrBeingWithdrawn() * rsrAmount + (rsrBalance - 1)) / rsrBalance; if ( - withdrawalRSRtoTake == 0 || rsrBeingWithdrawn() - withdrawalRSRtoTake < MIN_EXCHANGE_RATE.mulu_toUint(stakeBeingWithdrawn()) ) { @@ -386,8 +382,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { address account = accounts.at(i); delete withdrawals[account]; } - draftEra++; - emit AllUnstakingReset(draftEra); + emit AllUnstakingReset(era); } /// @custom:governance diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 6fe87988d1..a71df6c027 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.sellLow); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, trade.prices.sellHigh); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] @@ -145,7 +145,7 @@ library TradingLibP0 { /// 2. Stay under stack limit with fewer vars /// 3. Cache information such as component addresses to save on gas - struct TradingContextP0 { + struct TradingContext { BasketRange basketsHeld; // {BU} // basketsHeld.top is the number of partial baskets units held // basketsHeld.bottom is the number of full basket units held @@ -190,7 +190,7 @@ library TradingLibP0 { // === Prepare cached values === IMain main = bm.main(); - TradingContextP0 memory ctx = TradingContextP0({ + TradingContext memory ctx = TradingContext({ basketsHeld: basketsHeld, bm: bm, bh: main.basketHandler(), @@ -241,9 +241,14 @@ library TradingLibP0 { // token balances requiring trading vs not requiring trading. Seek to decrease uncertainty // the largest amount possible with each trade. // - // Algorithm Invariant: every increase of basketsHeld.bottom causes basketsRange().low to - // reach a new maximum. Note that basketRange().low may decrease slightly along the way. - // Assumptions: constant oracle prices; monotonically increasing refPerTok; no supply changes + // How do we know this algorithm converges? + // Assumption: constant oracle prices; monotonically increasing refPerTok() + // Any volume traded narrows the BU band. Why: + // - We might increase `basketsHeld.bottom` from run-to-run, but will never decrease it + // - We might decrease the UoA amount of excess balances beyond `basketsHeld.bottom` from + // run-to-run, but will never increase it + // - We might decrease the UoA amount of missing balances up-to `basketsHeld.top` from + // run-to-run, but will never increase it // // Preconditions: // - ctx is correctly populated, with current basketsHeld.bottom + basketsHeld.top @@ -264,12 +269,12 @@ library TradingLibP0 { // - range.bottom = min(rToken.basketsNeeded, basketsHeld.bottom + least baskets purchaseable) // where "least baskets purchaseable" involves trading at the worst price, // incurring the full maxTradeSlippage, and taking up to a minTradeVolume loss due to dust. - function basketRange(TradingContextP0 memory ctx, IERC20[] memory erc20s) + function basketRange(TradingContext memory ctx, IERC20[] memory erc20s) internal view returns (BasketRange memory range) { - (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU} + (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.lotPrice(); // {UoA/BU} // Cap ctx.basketsHeld.top if (ctx.basketsHeld.top > ctx.rToken.basketsNeeded()) { @@ -298,15 +303,21 @@ library TradingLibP0 { bal = bal.plus(asset.bal(address(ctx.stRSR))); } - (uint192 low, uint192 high) = asset.price(); // {UoA/tok} - // low decays down; high decays up + { + // 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; + } - // 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; + (uint192 low, uint192 high) = asset.price(); // {UoA/tok} + // price() is better than lotPrice() here: it's important to not underestimate how + // much value could be in a token that is unpriced by using a decaying high lotPrice. + // price() will return [0, FIX_MAX] in this case, which is preferable. // throughout these sections +/- is same as Fix.plus/Fix.minus and is Fix.gt/.lt @@ -343,7 +354,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, + // 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 // 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 @@ -423,12 +434,10 @@ library TradingLibP0 { // Sell IFFY last because it may recover value in the future. // All collateral in the basket have already been guaranteed to be SOUND by upstream checks. function nextTradePair( - TradingContextP0 memory ctx, + TradingContext memory ctx, IERC20[] memory erc20s, BasketRange memory range ) private view returns (TradeInfo memory trade) { - // assert(tradesOpen == 0); // guaranteed by BackingManager.rebalance() - MaxSurplusDeficit memory maxes; maxes.surplusStatus = CollateralStatus.IFFY; // least-desirable sell status @@ -444,19 +453,19 @@ library TradingLibP0 { // {tok} = {BU} * {tok/BU} uint192 needed = range.top.mul(ctx.bh.quantity(erc20s[i]), CEIL); // {tok} if (bal.gt(needed)) { - (uint192 low, uint192 high) = asset.price(); // {UoA/sellTok} - if (high == 0) continue; // Skip worthless assets + (uint192 lotLow, uint192 lotHigh) = asset.lotPrice(); // {UoA/sellTok} + if (lotHigh == 0) continue; // Skip worthless assets // by calculating this early we can duck the stack limit but be less gas-efficient bool enoughToSell = isEnoughToSell( asset, bal.minus(needed), - low, + lotLow, ctx.minTradeVolume ); // {UoA} = {sellTok} * {UoA/sellTok} - uint192 delta = bal.minus(needed).mul(low, FLOOR); + uint192 delta = bal.minus(needed).mul(lotLow, FLOOR); // status = asset.status() if asset.isCollateral() else SOUND CollateralStatus status; // starts SOUND @@ -467,8 +476,8 @@ library TradingLibP0 { if (isBetterSurplus(maxes, status, delta) && enoughToSell) { trade.sell = asset; trade.sellAmount = bal.minus(needed); - trade.prices.sellLow = low; - trade.prices.sellHigh = high; + trade.prices.sellLow = lotLow; + trade.prices.sellHigh = lotHigh; maxes.surplusStatus = status; maxes.surplus = delta; @@ -478,17 +487,17 @@ library TradingLibP0 { needed = range.bottom.mul(ctx.bh.quantity(erc20s[i]), CEIL); // {buyTok}; if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} - (uint192 low, uint192 high) = asset.price(); // {UoA/buyTok} + (uint192 lotLow, uint192 lotHigh) = asset.lotPrice(); // {UoA/buyTok} // {UoA} = {buyTok} * {UoA/buyTok} - uint192 delta = amtShort.mul(high, CEIL); + uint192 delta = amtShort.mul(lotHigh, CEIL); // The best asset to buy is whichever asset has the largest deficit if (delta.gt(maxes.deficit)) { trade.buy = ICollateral(address(asset)); trade.buyAmount = amtShort; - trade.prices.buyLow = low; - trade.prices.buyHigh = high; + trade.prices.buyLow = lotLow; + trade.prices.buyHigh = lotHigh; maxes.deficit = delta; } @@ -503,13 +512,13 @@ library TradingLibP0 { uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus( rsrAsset.bal(address(ctx.stRSR)) ); - (uint192 low, uint192 high) = rsrAsset.price(); // {UoA/RSR} + (uint192 lotLow, uint192 lotHigh) = rsrAsset.lotPrice(); // {UoA/RSR} - if (high > 0 && isEnoughToSell(rsrAsset, rsrAvailable, low, ctx.minTradeVolume)) { + if (lotHigh > 0 && isEnoughToSell(rsrAsset, rsrAvailable, lotLow, ctx.minTradeVolume)) { trade.sell = rsrAsset; trade.sellAmount = rsrAvailable; - trade.prices.sellLow = low; - trade.prices.sellHigh = high; + trade.prices.sellLow = lotLow; + trade.prices.sellHigh = lotHigh; } } } diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index c556a96120..098e47f93a 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -57,8 +57,6 @@ 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 b7191aa097..a64ff3f412 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -45,9 +45,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { IFurnace private furnace; mapping(TradeKind => uint48) private tradeEnd; // {s} last endTime() of an auction per kind - // === 3.1.0 === - mapping(IERC20 => uint192) private tokensOut; // {tok} token balances out in ITrades - // ==== Invariants ==== // tradingDelay <= MAX_TRADING_DELAY and backingBuffer <= MAX_BACKING_BUFFER @@ -93,7 +90,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// @return trade The ITrade contract settled /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { - delete tokensOut[sell]; trade = super.settleTrade(sell); // nonReentrant // if the settler is the trade contract itself, try chaining with another rebalance() @@ -117,6 +113,7 @@ 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( @@ -152,26 +149,22 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * rToken.basketsNeeded to the current basket holdings. Haircut time. */ - (TradingContext memory ctx, Registry memory reg) = tradingContext(basketsHeld); ( bool doTrade, TradeRequest memory req, TradePrices memory prices - ) = RecollateralizationLibP1.prepareRecollateralizationTrade(ctx, reg); + ) = RecollateralizationLibP1.prepareRecollateralizationTrade(this, basketsHeld); if (doTrade) { - IERC20 sellERC20 = req.sell.erc20(); - // Seize RSR if needed - if (sellERC20 == rsr) { - uint256 bal = sellERC20.balanceOf(address(this)); + if (req.sell.erc20() == rsr) { + uint256 bal = req.sell.erc20().balanceOf(address(this)); if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); } // Execute Trade ITrade trade = tryTrade(kind, req, prices); - tradeEnd[kind] = trade.endTime(); // {s} - tokensOut[sellERC20] = trade.sellAmount(); // {tok} + tradeEnd[kind] = trade.endTime(); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); @@ -191,6 +184,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { require(ArrayLib.allUnique(erc20s), "duplicate tokens"); assetRegistry.refresh(); + furnace.melt(); BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); @@ -218,22 +212,19 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * RToken traders according to the distribution totals. */ - // Forward any RSR held to StRSR pool and payout rewards - // RSR should never be sold for RToken yield + // Forward any RSR held to StRSR pool; RSR should never be sold for RToken yield if (rsr.balanceOf(address(this)) > 0) { // For CEI, this is an interaction "within our system" even though RSR is already live IERC20(address(rsr)).safeTransfer(address(stRSR), rsr.balanceOf(address(this))); - stRSR.payoutRewards(); } // Mint revenue RToken // Keep backingBuffer worth of collateral before recognizing revenue - 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} + if (basketsHeld.bottom > needed) { + rToken.mint(basketsHeld.bottom - needed); + needed = rToken.basketsNeeded().mul(FIX_ONE + backingBuffer); // keep buffer + } // At this point, even though basketsNeeded may have changed, we are: // - We're fully collateralized @@ -254,7 +245,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // delta: {qTok}, the excess quantity of this asset that we hold uint256 delta = bal.minus(req).shiftl_toUint(int8(asset.erc20Decimals())); uint256 tokensPerShare = delta / (totals.rTokenTotal + totals.rsrTotal); - if (tokensPerShare == 0) continue; // no div-by-0: Distributor guarantees (totals.rTokenTotal + totals.rsrTotal) > 0 // initial division is intentional here! We'd rather save the dust than be unfair @@ -273,40 +263,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // It's okay if there is leftover dust for RToken or a surplus asset (not RSR) } - // === View === - - /// Structs for trading - /// @param basketsHeld The number of baskets held by the BackingManager - /// @return ctx The TradingContext - /// @return reg Contents of AssetRegistry.getRegistry() - function tradingContext(BasketRange memory basketsHeld) - public - view - returns (TradingContext memory ctx, Registry memory reg) - { - reg = assetRegistry.getRegistry(); - - ctx.basketsHeld = basketsHeld; - ctx.bh = basketHandler; - ctx.ar = assetRegistry; - ctx.stRSR = stRSR; - ctx.rsr = rsr; - ctx.rToken = rToken; - ctx.minTradeVolume = minTradeVolume; - ctx.maxTradeSlippage = maxTradeSlippage; - ctx.quantities = new uint192[](reg.erc20s.length); - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - ctx.quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); - } - ctx.bals = new uint192[](reg.erc20s.length); - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - ctx.bals[i] = reg.assets[i].bal(address(this)) + tokensOut[reg.erc20s[i]]; - - // include StRSR's balance for RSR - if (reg.erc20s[i] == rsr) ctx.bals[i] += reg.assets[i].bal(address(stRSR)); - } - } - // === Private === /// Compromise on how many baskets are needed in order to recollateralize-by-accounting @@ -351,5 +307,5 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[38] private __gap; + uint256[39] private __gap; } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index fa076253bd..2cb493d1a3 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -121,8 +121,6 @@ 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 @@ -139,7 +137,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { require( main.hasRole(OWNER, _msgSender()) || - (lastStatus == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), + (status() == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), "basket unrefreshable" ); _switchBasket(); @@ -320,7 +318,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { /// Should not revert /// lowLow should be nonzero when the asset might be worth selling - /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility /// @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) @@ -424,7 +421,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 k = 0; k < len; ++k) { if (b.erc20s[j] == erc20sAll[k]) { erc20Index = k; - break; + continue; } } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 0111d25bc3..cfc6100a93 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -63,10 +63,6 @@ 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 @@ -85,7 +81,10 @@ contract BrokerP1 is ComponentP1, IBroker { uint48 dutchAuctionLength_ ) external initializer { __Component_init(main_); - cacheComponents(); + + backingManager = main_.backingManager(); + rsrTrader = main_.rsrTrader(); + rTokenTrader = main_.rTokenTrader(); setGnosis(gnosis_); setBatchTradeImplementation(batchTradeImplementation_); @@ -94,14 +93,6 @@ 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 @@ -136,9 +127,9 @@ contract BrokerP1 is ComponentP1, IBroker { /// Disable the broker until re-enabled by governance /// @custom:protected - // checks: caller is a Trade this contract cloned + // checks: not paused (trading), not frozen, caller is a Trade this contract cloned // effects: disabled' = true - function reportViolation() external { + function reportViolation() external notTradingPausedOrFrozen { require(trades[_msgSender()], "unrecognized trade contract"); ITrade trade = ITrade(_msgSender()); TradeKind kind = trade.KIND(); @@ -265,11 +256,6 @@ 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; @@ -284,15 +270,10 @@ 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[41] private __gap; + uint256[42] private __gap; } diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 776e19fe5a..ca818f5a14 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -57,17 +57,13 @@ 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; } @@ -98,8 +94,8 @@ contract DistributorP1 is ComponentP1, IDistributor { { RevenueTotals memory revTotals = totals(); uint256 totalShares = isRSR ? revTotals.rsrTotal : revTotals.rTokenTotal; - if (totalShares > 0) tokensPerShare = amount / totalShares; - require(tokensPerShare > 0, "nothing to distribute"); + require(totalShares > 0, "nothing to distribute"); + tokensPerShare = amount / totalShares; } // Evenly distribute revenue tokens per distribution share. @@ -111,8 +107,6 @@ 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); @@ -124,13 +118,15 @@ 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({ addrTo: addrTo, amount: transferAmt }); + transfers[numTransfers] = Transfer({ + erc20: erc20, + addrTo: addrTo, + amount: transferAmt + }); numTransfers++; } emit RevenueDistributed(erc20, caller, amount); @@ -138,16 +134,7 @@ contract DistributorP1 is ComponentP1, IDistributor { // == Interactions == for (uint256 i = 0; i < numTransfers; i++) { Transfer memory t = transfers[i]; - IERC20Upgradeable(address(erc20)).safeTransferFrom(caller, t.addrTo, t.amount); - } - - // Perform reward accounting - if (accountRewards) { - if (isRSR) { - main.stRSR().payoutRewards(); - } else { - main.furnace().melt(); - } + IERC20Upgradeable(address(t.erc20)).safeTransferFrom(caller, t.addrTo, t.amount); } } diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index 63dcc695d4..923ba33737 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() public { + function melt() external notFrozen { if (uint48(block.timestamp) < uint64(lastPayout) + PERIOD) return; // # of whole periods that have passed since lastPayout @@ -90,9 +90,15 @@ 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 21781ca082..43bddcaed7 100644 --- a/contracts/p1/Main.sol +++ b/contracts/p1/Main.sol @@ -43,9 +43,10 @@ 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(); // runs furnace.melt() + assetRegistry.refresh(); // == CE block == + if (!frozen()) furnace.melt(); stRSR.payoutRewards(); } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 1c07b650ef..8b447d4273 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -108,6 +108,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); + furnace.melt(); // == Checks-effects block == @@ -181,6 +182,8 @@ 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 == @@ -252,6 +255,8 @@ 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 998bdc951f..43253c6b0e 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -53,12 +53,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant - - // 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 - } + _distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -112,12 +107,6 @@ 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 @@ -134,8 +123,10 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { IAsset assetToBuy = assetRegistry.toAsset(tokenToBuy); // Refresh everything if RToken is involved - if (involvesRToken) assetRegistry.refresh(); - else { + if (involvesRToken) { + assetRegistry.refresh(); + furnace.melt(); + } else { // Otherwise: refresh just the needed assets and nothing more for (uint256 i = 0; i < len; ++i) { assetRegistry.toAsset(erc20s[i]).refresh(); @@ -144,7 +135,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } // Cache and validate buyHigh - (uint192 buyLow, uint192 buyHigh) = assetToBuy.price(); // {UoA/tok} + (uint192 buyLow, uint192 buyHigh) = assetToBuy.lotPrice(); // {UoA/tok} require(buyHigh > 0 && buyHigh < FIX_MAX, "buy asset price unknown"); // For each ERC20 that isn't the tokenToBuy, start an auction of the given kind @@ -156,7 +147,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { require(erc20.balanceOf(address(this)) > 0, "0 balance"); IAsset assetToSell = assetRegistry.toAsset(erc20); - (uint192 sellLow, uint192 sellHigh) = assetToSell.price(); // {UoA/tok} + (uint192 sellLow, uint192 sellHigh) = assetToSell.lotPrice(); // {UoA/tok} TradeInfo memory trade = TradeInfo({ sell: assetToSell, diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index faff182759..527d63f50c 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -76,15 +76,15 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // === Financial State: Drafts === // Era. If drafts get wiped out due to RSR seizure, increment the era to zero draft values. // Only ever directly written by beginDraftEra() - uint256 internal draftEra; // {draftEra} + uint256 internal draftEra; // Drafts: share of the withdrawing tokens. Not transferrable and not revenue-earning. struct CumulativeDraft { // Avoid re-using uint192 in order to avoid confusion with our type system; 176 is enough uint176 drafts; // Total amount of drafts that will become available // {qDrafts} uint64 availableAt; // When the last of the drafts will become available } - // {draftEra} => ({account} => {qDrafts}) - mapping(uint256 => mapping(address => CumulativeDraft[])) public draftQueues; // {qDrafts} + // draftEra => ({account} => {drafts}) + mapping(uint256 => mapping(address => CumulativeDraft[])) public draftQueues; // {drafts} mapping(uint256 => mapping(address => uint256)) public firstRemainingDraft; // draft index uint256 private totalDrafts; // Total of all drafts {qDrafts} uint256 private draftRSR; // Amount of RSR backing all drafts {qRSR} @@ -285,7 +285,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // Create draft (uint256 index, uint64 availableAt) = pushDraft(account, rsrAmount); - emit UnstakingStarted(index, draftEra, account, rsrAmount, stakeAmount, availableAt); + emit UnstakingStarted(index, era, account, rsrAmount, stakeAmount, availableAt); } /// Complete an account's unstaking; callable by anyone @@ -564,11 +564,6 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab return totalDrafts; } - /// @return {draftEra} The current era for drafts (withdrawals) - function getDraftEra() external view returns (uint256) { - return draftEra; - } - // ==== Internal Functions ==== /// Assign reward payouts to the staker pool diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 8edb10f86c..dd86e45ca1 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -8,6 +8,29 @@ import "../../interfaces/IBackingManager.sol"; import "../../libraries/Fixed.sol"; 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 +struct TradingContext { + BasketRange basketsHeld; // {BU} + // basketsHeld.top is the number of partial baskets units held + // basketsHeld.bottom is the number of full basket units held + + // Components + IBackingManager bm; + IBasketHandler bh; + IAssetRegistry ar; + IStRSR stRSR; + IERC20 rsr; + IRToken rToken; + // Gov Vars + uint192 minTradeVolume; // {UoA} + uint192 maxTradeSlippage; // {1} + // Cached values + uint192[] quantities; // {tok/BU} basket quantities +} + /** * @title RecollateralizationLibP1 * @notice An informal extension of BackingManager that implements the rebalancing logic @@ -33,7 +56,7 @@ library RecollateralizationLibP1 { // let trade = nextTradePair(...) // if trade.sell is not a defaulted collateral, prepareTradeToCoverDeficit(...) // otherwise, prepareTradeSell(...) taking the minBuyAmount as the dependent variable - function prepareRecollateralizationTrade(TradingContext memory ctx, Registry memory reg) + function prepareRecollateralizationTrade(IBackingManager bm, BasketRange memory basketsHeld) external view returns ( @@ -42,8 +65,31 @@ library RecollateralizationLibP1 { TradePrices memory prices ) { + IMain main = bm.main(); + + // === Prepare TradingContext cache === + TradingContext memory ctx; + + ctx.basketsHeld = basketsHeld; + ctx.bm = bm; + ctx.bh = main.basketHandler(); + ctx.ar = main.assetRegistry(); + ctx.stRSR = main.stRSR(); + ctx.rsr = main.rsr(); + ctx.rToken = main.rToken(); + ctx.minTradeVolume = bm.minTradeVolume(); + ctx.maxTradeSlippage = bm.maxTradeSlippage(); + + // Calculate quantities + Registry memory reg = ctx.ar.getRegistry(); + ctx.quantities = new uint192[](reg.erc20s.length); + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + ctx.quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + } + + // ============================ + // 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 @@ -85,17 +131,22 @@ library RecollateralizationLibP1 { // token balances requiring trading vs not requiring trading. Seek to decrease uncertainty // the largest amount possible with each trade. // - // Algorithm Invariant: every increase of basketsHeld.bottom causes basketsRange().low to - // reach a new maximum. Note that basketRange().low may decrease slightly along the way. - // Assumptions: constant oracle prices; monotonically increasing refPerTok; no supply changes + // How do we know this algorithm converges? + // Assumption: constant oracle prices; monotonically increasing refPerTok() + // Any volume traded narrows the BU band. Why: + // - We might increase `basketsHeld.bottom` from run-to-run, but will never decrease it + // - We might decrease the UoA amount of excess balances beyond `basketsHeld.bottom` from + // run-to-run, but will never increase it + // - We might decrease the UoA amount of missing balances up-to `basketsHeld.top` from + // run-to-run, but will never increase it // // Preconditions: // - ctx is correctly populated, with current basketsHeld.bottom + basketsHeld.top // - reg contains erc20 + asset + quantities arrays in same order and without duplicates // Trading Strategy: // - We will not aim to hold more than rToken.basketsNeeded() BUs - // - No double trades: capital converted from token A to token B should not go to token C - // unless the clearing price was outside the expected price range + // - 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 // - 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 ) @@ -113,12 +164,7 @@ library RecollateralizationLibP1 { view returns (BasketRange memory range) { - // tradesOpen will be 0 when called by prepareRecollateralizationTrade() - // tradesOpen can be > 0 when called by RTokenAsset.basketRange() - - (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU} - require(buPriceLow > 0 && buPriceHigh < FIX_MAX, "BUs unpriced"); - + (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.lotPrice(); // {UoA/BU} uint192 basketsNeeded = ctx.rToken.basketsNeeded(); // {BU} // Cap ctx.basketsHeld.top @@ -143,17 +189,28 @@ library RecollateralizationLibP1 { // Exclude RToken balances to avoid double counting value if (reg.erc20s[i] == IERC20(address(ctx.rToken))) continue; - (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/tok} + uint192 bal = reg.assets[i].bal(address(ctx.bm)); // {tok} - // 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], ctx.bals[i], low, ctx.minTradeVolume) - ) { - continue; + // For RSR, include the staking balance + if (reg.erc20s[i] == ctx.rsr) { + bal = bal.plus(reg.assets[i].bal(address(ctx.stRSR))); + } + + if (ctx.quantities[i] == 0) { + // Skip over dust-balance assets not in the basket + (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} + + // Intentionally include value of IFFY/DISABLED collateral + if (!TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume)) { + continue; + } } + (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/tok} + // price() is better than lotPrice() here: it's important to not underestimate how + // much value could be in a token that is unpriced by using a decaying high lotPrice. + // price() will return [0, FIX_MAX] in this case, which is preferable. + // throughout these sections +/- is same as Fix.plus/Fix.minus and is Fix.gt/.lt // deltaTop: optimistic case @@ -163,21 +220,17 @@ library RecollateralizationLibP1 { // {tok} = {tok/BU} * {BU} uint192 anchor = ctx.quantities[i].mul(ctx.basketsHeld.top, CEIL); - if (anchor > ctx.bals[i]) { + if (anchor > bal) { // deficit: deduct optimistic estimate of baskets missing // {BU} = {UoA/tok} * {tok} / {UoA/BU} - deltaTop -= int256( - uint256(low.mulDiv(anchor - ctx.bals[i], buPriceHigh, FLOOR)) - ); + deltaTop -= int256(uint256(low.mulDiv(anchor - bal, buPriceHigh, FLOOR))); // does not need underflow protection: using low price of asset } else { // surplus: add-in optimistic estimate of baskets purchaseable // {BU} = {UoA/tok} * {tok} / {UoA/BU} - deltaTop += int256( - uint256(high.safeMulDiv(ctx.bals[i] - anchor, buPriceLow, CEIL)) - ); + deltaTop += int256(uint256(high.safeMulDiv(bal - anchor, buPriceLow, CEIL))); } } @@ -189,12 +242,12 @@ library RecollateralizationLibP1 { // (1) Sum token value at low price // {UoA} = {UoA/tok} * {tok} - uint192 val = low.mul(ctx.bals[i] - anchor, FLOOR); + uint192 val = low.mul(bal - anchor, FLOOR); // (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, - // so min trade volumes are always assessed based on low prices. At this point + // 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 // 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. @@ -252,9 +305,9 @@ library RecollateralizationLibP1 { /// prices.buyLow {UoA/buyTok} The best-case price of the buy token on secondary markets /// prices.buyHigh {UoA/buyTok} The worst-case price of the buy token on secondary markets /// - // 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 + // 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 // // First, ignoring RSR: // `trade.sell` is the token from erc20s with the greatest surplus value (in UoA), @@ -277,33 +330,26 @@ library RecollateralizationLibP1 { Registry memory reg, BasketRange memory range ) private view returns (TradeInfo memory trade) { - // assert(tradesOpen == 0); // guaranteed by BackingManager.rebalance() - MaxSurplusDeficit memory maxes; maxes.surplusStatus = CollateralStatus.IFFY; // least-desirable sell status - uint256 rsrIndex = reg.erc20s.length; // invalid index, to-start - // Iterate over non-RSR/non-RToken assets // (no space on the stack to cache erc20s.length) for (uint256 i = 0; i < reg.erc20s.length; ++i) { - if (address(reg.erc20s[i]) == address(ctx.rToken)) continue; - else if (reg.erc20s[i] == ctx.rsr) { - rsrIndex = i; - continue; - } + if (reg.erc20s[i] == ctx.rsr || address(reg.erc20s[i]) == address(ctx.rToken)) continue; + + uint192 bal = reg.assets[i].bal(address(ctx.bm)); // {tok} // {tok} = {BU} * {tok/BU} // needed(Top): token balance needed for range.top baskets: quantity(e) * range.top uint192 needed = range.top.mul(ctx.quantities[i], CEIL); // {tok} - if (ctx.bals[i].gt(needed)) { - (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/sellTok} - - if (high == 0) continue; // skip over worthless assets + if (bal.gt(needed)) { + (uint192 lotLow, uint192 lotHigh) = reg.assets[i].lotPrice(); // {UoA/sellTok} + if (lotHigh == 0) continue; // skip over worthless assets // {UoA} = {sellTok} * {UoA/sellTok} - uint192 delta = ctx.bals[i].minus(needed).mul(low, FLOOR); + uint192 delta = bal.minus(needed).mul(lotLow, FLOOR); // status = asset.status() if asset.isCollateral() else SOUND CollateralStatus status; // starts SOUND @@ -317,15 +363,15 @@ library RecollateralizationLibP1 { isBetterSurplus(maxes, status, delta) && TradeLib.isEnoughToSell( reg.assets[i], - ctx.bals[i].minus(needed), - low, + bal.minus(needed), + lotLow, ctx.minTradeVolume ) ) { trade.sell = reg.assets[i]; - trade.sellAmount = ctx.bals[i].minus(needed); - trade.prices.sellLow = low; - trade.prices.sellHigh = high; + trade.sellAmount = bal.minus(needed); + trade.prices.sellLow = lotLow; + trade.prices.sellHigh = lotHigh; maxes.surplusStatus = status; maxes.surplus = delta; @@ -334,19 +380,19 @@ library RecollateralizationLibP1 { // needed(Bottom): token balance needed at bottom of the basket range needed = range.bottom.mul(ctx.quantities[i], CEIL); // {buyTok}; - if (ctx.bals[i].lt(needed)) { - uint192 amtShort = needed.minus(ctx.bals[i]); // {buyTok} - (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/buyTok} + if (bal.lt(needed)) { + uint192 amtShort = needed.minus(bal); // {buyTok} + (uint192 lotLow, uint192 lotHigh) = reg.assets[i].lotPrice(); // {UoA/buyTok} // {UoA} = {buyTok} * {UoA/buyTok} - uint192 delta = amtShort.mul(high, CEIL); + uint192 delta = amtShort.mul(lotHigh, CEIL); // The best asset to buy is whichever asset has the largest deficit if (delta.gt(maxes.deficit)) { trade.buy = reg.assets[i]; trade.buyAmount = amtShort; - trade.prices.buyLow = low; - trade.prices.buyHigh = high; + trade.prices.buyLow = lotLow; + trade.prices.buyHigh = lotHigh; maxes.deficit = delta; } @@ -356,22 +402,21 @@ library RecollateralizationLibP1 { // Use RSR if needed if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { - (uint192 low, uint192 high) = reg.assets[rsrIndex].price(); // {UoA/RSR} + IAsset rsrAsset = ctx.ar.toAsset(ctx.rsr); + + uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus( + rsrAsset.bal(address(ctx.stRSR)) + ); + (uint192 lotLow, uint192 lotHigh) = rsrAsset.lotPrice(); // {UoA/RSR} - // if rsr does not have a registered asset the below array accesses will revert if ( - high > 0 && - TradeLib.isEnoughToSell( - reg.assets[rsrIndex], - ctx.bals[rsrIndex], - low, - ctx.minTradeVolume - ) + lotHigh > 0 && + TradeLib.isEnoughToSell(rsrAsset, rsrAvailable, lotLow, ctx.minTradeVolume) ) { - trade.sell = reg.assets[rsrIndex]; - trade.sellAmount = ctx.bals[rsrIndex]; - trade.prices.sellLow = low; - trade.prices.sellHigh = high; + trade.sell = rsrAsset; + trade.sellAmount = rsrAvailable; + trade.prices.sellLow = lotLow; + trade.prices.sellHigh = lotHigh; } } } diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index 8d3c8e01c9..f0921dd511 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.sellLow); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, trade.prices.sellHigh); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 1c6217e8ec..24b2044d38 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -97,7 +97,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // == Interactions == (uint256 soldAmt, uint256 boughtAmt) = trade.settle(); - emit TradeSettled(trade, sell, trade.buy(), soldAmt, boughtAmt); + emit TradeSettled(trade, trade.sell(), trade.buy(), soldAmt, boughtAmt); } /// Try to initiate a trade with a trading partner provided by the broker @@ -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)); // ensure calling class has checked this + assert(address(trades[sell]) == address(0)); // 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 60e575cf71..bf7cef6022 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 = underlyingRefPerTok; + exposedReferencePrice = hiddenReferencePrice; markStatus(CollateralStatus.DISABLED); } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index 302a6a6731..1bb044c239 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -7,14 +7,10 @@ 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; @@ -42,7 +38,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 price(), should be highest of + /// @dev oracleTimeout_ is also used as the timeout value in lotPrice(), should be highest of /// all assets' oracleTimeout in a collateral if there are multiple oracles constructor( uint48 priceTimeout_, @@ -64,7 +60,7 @@ contract Asset is IAsset, VersionedAsset { erc20 = erc20_; erc20Decimals = erc20.decimals(); maxTradeVolume = maxTradeVolume_; - oracleTimeout = oracleTimeout_ + ORACLE_TIMEOUT_BUFFER; // add 300s as a buffer + oracleTimeout = oracleTimeout_; } /// Can revert, used by other contract functions in order to catch errors @@ -112,69 +108,54 @@ 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 _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) { + /// @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) { try this.tryPrice() returns (uint192 low, uint192 high, uint192) { // if the price feed is still functioning, use that - _low = low; - _high = 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 - // if the price feed is broken, decay _low downwards and _high upwards + // if the price feed is broken, use a decayed historical value uint48 delta = uint48(block.timestamp) - lastSave; // {s} if (delta <= oracleTimeout) { - // use saved prices for at least the oracleTimeout - _low = savedLowPrice; - _high = savedHighPrice; + lotLow = savedLowPrice; + lotHigh = savedHighPrice; } else if (delta >= oracleTimeout + priceTimeout) { - // unpriced after a full timeout - return (0, FIX_MAX); + return (0, 0); // no price after full timeout } else { // oracleTimeout <= delta <= oracleTimeout + priceTimeout - // Decay _high upwards to 3x savedHighPrice + // {1} = {s} / {s} + uint192 lotMultiplier = divuu(oracleTimeout + priceTimeout - delta, priceTimeout); + // {UoA/tok} = {UoA/tok} * {1} - _high = savedHighPrice.safeMul( - FIX_ONE + MAX_HIGH_PRICE_BUFFER.muluDivu(delta - oracleTimeout, priceTimeout), - ROUND - ); // during overflow should not revert - - // 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 - } + lotLow = savedLowPrice.mul(lotMultiplier); + lotHigh = savedHighPrice.mul(lotMultiplier); } } - assert(_low <= _high); - } - - /// Should not revert - /// lotLow should be nonzero when the asset might be worth selling - /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility - /// @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 price(); + assert(lotLow <= lotHigh); } /// @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 dfc36ff73e..67d0c12f34 100644 --- a/contracts/plugins/assets/EURFiatCollateral.sol +++ b/contracts/plugins/assets/EURFiatCollateral.sol @@ -27,8 +27,6 @@ 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 d3afad43c5..9110117bc5 100644 --- a/contracts/plugins/assets/FiatCollateral.sol +++ b/contracts/plugins/assets/FiatCollateral.sol @@ -75,10 +75,6 @@ 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; @@ -126,7 +122,7 @@ contract FiatCollateral is ICollateral, Asset { function refresh() public virtual override(Asset, IAsset) { CollateralStatus oldStatus = status(); - // Check for soft default + save price + // Check for soft default + save lotPrice 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 60b0bd8329..0fc8e40884 100644 --- a/contracts/plugins/assets/L2LSDCollateral.sol +++ b/contracts/plugins/assets/L2LSDCollateral.sol @@ -30,7 +30,6 @@ 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; @@ -53,7 +52,7 @@ abstract contract L2LSDCollateral is AppreciatingFiatCollateral { // uint192(<) is equivalent to Fix.lt if (underlyingRefPerTok < exposedReferencePrice) { - exposedReferencePrice = underlyingRefPerTok; + exposedReferencePrice = hiddenReferencePrice; markStatus(CollateralStatus.DISABLED); } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; diff --git a/contracts/plugins/assets/NonFiatCollateral.sol b/contracts/plugins/assets/NonFiatCollateral.sol index 1923dea24a..2e6b3c531f 100644 --- a/contracts/plugins/assets/NonFiatCollateral.sol +++ b/contracts/plugins/assets/NonFiatCollateral.sol @@ -27,8 +27,6 @@ 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 e9487fe671..68a9da9863 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -18,14 +18,12 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { using OracleLib for AggregatorV3Interface; // Component addresses are not mutable in protocol, so it's safe to cache these - IAssetRegistry public immutable assetRegistry; + IMain public immutable main; IBasketHandler public immutable basketHandler; + IAssetRegistry public immutable assetRegistry; IBackingManager public immutable backingManager; - IFurnace public immutable furnace; - IERC20 public immutable rsr; - IStRSR public immutable stRSR; - IERC20Metadata public immutable erc20; // The RToken + IERC20Metadata public immutable erc20; uint8 public immutable erc20Decimals; @@ -39,13 +37,10 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { require(address(erc20_) != address(0), "missing erc20"); require(maxTradeVolume_ > 0, "invalid max trade volume"); - IMain main = erc20_.main(); - assetRegistry = main.assetRegistry(); + main = erc20_.main(); basketHandler = main.basketHandler(); + assetRegistry = main.assetRegistry(); backingManager = main.backingManager(); - furnace = main.furnace(); - rsr = main.rsr(); - stRSR = main.stRSR(); erc20 = IERC20Metadata(address(erc20_)); erc20Decimals = erc20_.decimals(); @@ -60,8 +55,10 @@ 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() external view virtual returns (uint192 low, uint192 high) { - (uint192 lowBUPrice, uint192 highBUPrice) = basketHandler.price(); // {UoA/BU} + function tryPrice(bool useLotPrice) external view virtual returns (uint192 low, uint192 high) { + (uint192 lowBUPrice, uint192 highBUPrice) = useLotPrice + ? basketHandler.lotPrice() + : basketHandler.price(); // {UoA/BU} require(lowBUPrice != 0 && highBUPrice != FIX_MAX, "invalid price"); assert(lowBUPrice <= highBUPrice); // not obviously true just by inspection @@ -82,21 +79,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 saved prices - - furnace.melt(); - if (msg.sender != address(assetRegistry)) assetRegistry.refresh(); + // No need to save lastPrice; can piggyback off the backing collateral's lotPrice() 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() returns (uint192 low, uint192 high) { + try this.tryPrice(false) returns (uint192 low, uint192 high) { return (low, high); } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data @@ -107,11 +104,18 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// Should not revert /// lotLow should be nonzero when the asset might be worth selling - /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility + /// @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 virtual returns (uint192 lotLow, uint192 lotHigh) { - return price(); + 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 @@ -139,15 +143,10 @@ 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 ( @@ -159,17 +158,15 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { _updateCachedPrice(); } - rTokenPrice = cachedOracleData.cachedPrice; - updatedAt = cachedOracleData.cachedAtTime; + return (cachedOracleData.cachedPrice, cachedOracleData.cachedAtTime); } // ==== Private ==== // Update Oracle Data function _updateCachedPrice() internal { - assetRegistry.refresh(); // will call furnace.melt() - (uint192 low, uint192 high) = price(); + require(low != 0 && high != FIX_MAX, "invalid price"); cachedOracleData = CachedOracleData( @@ -181,7 +178,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { ); } - /// Computationally expensive basketRange calculation; used in price() + /// Computationally expensive basketRange calculation; used in price() & lotPrice() function basketRange() private view returns (BasketRange memory range) { BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} @@ -196,9 +193,24 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { // the absence of an external price feed. Any RToken that gets reasonably big // should switch over to an asset with a price feed. - (TradingContext memory ctx, Registry memory reg) = backingManager.tradingContext( - basketsHeld - ); + TradingContext memory ctx; + + ctx.basketsHeld = basketsHeld; + ctx.bm = backingManager; + ctx.bh = basketHandler; + ctx.ar = assetRegistry; + ctx.stRSR = main.stRSR(); + ctx.rsr = main.rsr(); + ctx.rToken = main.rToken(); + ctx.minTradeVolume = backingManager.minTradeVolume(); + ctx.maxTradeSlippage = backingManager.maxTradeSlippage(); + + // Calculate quantities + Registry memory reg = ctx.ar.getRegistry(); + ctx.quantities = new uint192[](reg.erc20s.length); + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + ctx.quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + } // will exclude UoA value from RToken balances at BackingManager range = RecollateralizationLibP1.basketRange(ctx, reg); diff --git a/contracts/plugins/assets/VersionedAsset.sol b/contracts/plugins/assets/VersionedAsset.sol index b36945769d..ac8371e7f2 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.1.0"; +string constant ASSET_VERSION = "3.0.1"; /** * @title VersionedAsset diff --git a/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol index ba1843351c..2edfd5d65b 100644 --- a/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol +++ b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol @@ -20,9 +20,7 @@ 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 14e72a72ca..f6e98c267e 100644 --- a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol +++ b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol @@ -41,9 +41,7 @@ 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 59e921e774..594db5465e 100644 --- a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol +++ b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol @@ -33,7 +33,6 @@ 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 40eb3a9d6e..5c190e6050 100644 --- a/contracts/plugins/assets/cbeth/CBETHCollateral.sol +++ b/contracts/plugins/assets/cbeth/CBETHCollateral.sol @@ -32,7 +32,6 @@ 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 4e98b7c3f2..b745028f54 100644 --- a/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol +++ b/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol @@ -41,7 +41,6 @@ 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 351074009d..15deec4f2c 100644 --- a/contracts/plugins/assets/cbeth/README.md +++ b/contracts/plugins/assets/cbeth/README.md @@ -15,7 +15,6 @@ 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 ce76a72635..a60744893a 100644 --- a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol @@ -29,8 +29,6 @@ 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 3d7dcae18f..f0a44584b5 100644 --- a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol @@ -30,7 +30,6 @@ 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/compoundv2/CTokenWrapper.sol b/contracts/plugins/assets/compoundv2/CTokenWrapper.sol index 27b37d8382..286787d42a 100644 --- a/contracts/plugins/assets/compoundv2/CTokenWrapper.sol +++ b/contracts/plugins/assets/compoundv2/CTokenWrapper.sol @@ -35,11 +35,9 @@ contract CTokenWrapper is RewardableERC20Wrapper { // === Overrides === function _claimAssetRewards() internal virtual override { - address[] memory holders = new address[](1); address[] memory cTokens = new address[](1); - holders[0] = address(this); cTokens[0] = address(underlying); - comptroller.claimComp(holders, cTokens, false, true); + comptroller.claimComp(address(this), cTokens); } // No overrides of _deposit()/_withdraw() necessary: no staking required diff --git a/contracts/plugins/assets/compoundv2/ICToken.sol b/contracts/plugins/assets/compoundv2/ICToken.sol index c83f9a3552..9dafd86c80 100644 --- a/contracts/plugins/assets/compoundv2/ICToken.sol +++ b/contracts/plugins/assets/compoundv2/ICToken.sol @@ -33,26 +33,10 @@ interface ICToken is IERC20Metadata { function redeem(uint256 redeemTokens) external returns (uint256); } -interface TestICToken is ICToken { - /** - * @notice Sender borrows assets from the protocol to their own address - * @param borrowAmount The amount of the underlying asset to borrow - * @return uint 0=success, otherwise a failure - */ - function borrow(uint256 borrowAmount) external returns (uint256); -} - interface IComptroller { /// Claim comp for an account, to an account - function claimComp( - address[] memory holders, - address[] memory cTokens, - bool borrowers, - bool suppliers - ) external; + function claimComp(address account, address[] memory cTokens) external; /// @return The address for COMP token function getCompAddress() external view returns (address); - - function enterMarkets(address[] calldata) external returns (uint256[] memory); } diff --git a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol index 17d46dc908..5e7bd1238c 100644 --- a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol +++ b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol @@ -19,6 +19,12 @@ import "./vendor/IComet.sol"; * UoA = USD */ contract CTokenV3Collateral is AppreciatingFiatCollateral { + struct CometCollateralConfig { + IERC20 rewardERC20; + uint256 reservesThresholdIffy; + uint256 reservesThresholdDisabled; + } + using OracleLib for AggregatorV3Interface; using FixLib for uint192; @@ -33,13 +39,16 @@ 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_; cometDecimals = comet.decimals(); } + function bal(address account) external view override(Asset, IAsset) returns (uint192) { + return shiftl_toFix(erc20.balanceOf(account), -int8(erc20Decimals)); + } + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external override(Asset, IRewardable) { IRewardable(address(erc20)).claimRewards(); @@ -67,7 +76,7 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { // uint192(<) is equivalent to Fix.lt if (underlyingRefPerTok < exposedReferencePrice) { - exposedReferencePrice = underlyingRefPerTok; + exposedReferencePrice = hiddenReferencePrice; markStatus(CollateralStatus.DISABLED); } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; diff --git a/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol b/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol index afbab80784..5b7b176061 100644 --- a/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol +++ b/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol @@ -43,7 +43,7 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { } /// @return number of decimals - function decimals() public pure override(IERC20Metadata, WrappedERC20) returns (uint8) { + function decimals() public pure override returns (uint8) { return 6; } @@ -81,7 +81,7 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { address dst, uint256 amount ) internal { - if (!underlyingComet.hasPermission(src, operator)) revert Unauthorized(); + if (!hasPermission(src, operator)) revert Unauthorized(); // {Comet} uint256 srcBal = underlyingComet.balanceOf(src); if (amount > srcBal) amount = srcBal; @@ -203,10 +203,7 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { rewardsClaimed[src] = accrued; rewardsAddr.claimTo(address(underlyingComet), address(this), address(this), true); - - uint256 bal = rewardERC20.balanceOf(address(this)); - if (owed > bal) owed = bal; - rewardERC20.safeTransfer(dst, owed); + IERC20(rewardERC20).safeTransfer(dst, owed); } emit RewardsClaimed(rewardERC20, owed); } diff --git a/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol b/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol index de2ab80ebe..89a9dcfb35 100644 --- a/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol +++ b/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol @@ -10,8 +10,8 @@ import "../../../interfaces/IRewardable.sol"; interface ICusdcV3Wrapper is IWrappedERC20, IRewardable { struct UserBasic { uint104 principal; - uint64 baseTrackingIndex; uint64 baseTrackingAccrued; + uint64 baseTrackingIndex; uint256 rewardsClaimed; } diff --git a/contracts/plugins/assets/compoundv3/WrappedERC20.sol b/contracts/plugins/assets/compoundv3/WrappedERC20.sol index 290a2da080..b3287711d7 100644 --- a/contracts/plugins/assets/compoundv3/WrappedERC20.sol +++ b/contracts/plugins/assets/compoundv3/WrappedERC20.sol @@ -75,13 +75,6 @@ abstract contract WrappedERC20 is IWrappedERC20 { return _symbol; } - /** - * @dev Returns the decimals places of the token. - */ - function decimals() public pure virtual returns (uint8) { - return 18; - } - /** * @dev See {IERC20-totalSupply}. */ diff --git a/contracts/plugins/assets/compoundv3/vendor/CometExtInterface.sol b/contracts/plugins/assets/compoundv3/vendor/CometExtInterface.sol index 70d9664aac..a144d69112 100644 --- a/contracts/plugins/assets/compoundv3/vendor/CometExtInterface.sol +++ b/contracts/plugins/assets/compoundv3/vendor/CometExtInterface.sol @@ -95,12 +95,4 @@ abstract contract CometExtInterface { function allowance(address owner, address spender) external view virtual returns (uint256); event Approval(address indexed owner, address indexed spender, uint256 amount); - - /** - * @notice Determine if the manager has permission to act on behalf of the owner - * @param owner The owner account - * @param manager The manager account - * @return Whether or not the manager has permission - */ - function hasPermission(address owner, address manager) external view virtual returns (bool); } diff --git a/contracts/plugins/assets/curve/CurveStableCollateral.sol b/contracts/plugins/assets/curve/CurveStableCollateral.sol index c59994fd56..5d6f985401 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 = underlyingRefPerTok; + exposedReferencePrice = hiddenReferencePrice; markStatus(CollateralStatus.DISABLED); } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; diff --git a/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol index ad3cd6ac8e..7fd4fe005b 100644 --- a/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol @@ -13,9 +13,6 @@ interface ICurveMetaPool is ICurvePool, IERC20Metadata { * This plugin contract is intended for 2-fiattoken stable metapools that * DO NOT involve RTokens, such as LUSD-fraxBP or MIM-3CRV. * - * Does not support older metapools that have a separate contract for the - * metapool's LP token. - * * tok = ConvexStakingWrapper(PairedUSDToken/USDBasePool) * ref = PairedUSDToken/USDBasePool pool invariant * tar = USD diff --git a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol index 780a083a8b..420e002f4a 100644 --- a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol @@ -42,11 +42,6 @@ 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/curve/crv/CurveGaugeWrapper.sol b/contracts/plugins/assets/curve/crv/CurveGaugeWrapper.sol index 8531894bb9..e4c893f024 100644 --- a/contracts/plugins/assets/curve/crv/CurveGaugeWrapper.sol +++ b/contracts/plugins/assets/curve/crv/CurveGaugeWrapper.sol @@ -17,16 +17,6 @@ interface ILiquidityGauge { function withdraw(uint256 _value) external; } -// Note: Only supports CRV rewards. If a Curve pool with multiple reward tokens is -// used, other reward tokens beyond CRV will never be claimed and distributed to -// depositors. These unclaimed rewards will be lost forever. - -// In addition to this, each wrapper deployment must be tested individually, regardless -// of the number of reward tokens it has. This contract is not compatible with all gauges -// and may revert depending on the Curve Gauge being used. For example, the -// `RewardsOnlyGauge` does not have a user_checkpoint() function, which means the -// MINTER.mint() call in this contract would revert in that case. - contract CurveGaugeWrapper is RewardableERC20Wrapper { using SafeERC20 for IERC20; @@ -56,7 +46,6 @@ contract CurveGaugeWrapper is RewardableERC20Wrapper { gauge.withdraw(_amount); } - // claim rewards - only supports CRV rewards, may not work for all gauges function _claimAssetRewards() internal virtual override { MINTER.mint(address(gauge)); } diff --git a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol index 6653f450c2..250d5b63ae 100644 --- a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol +++ b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol @@ -9,6 +9,7 @@ import "@openzeppelin/contracts-v0.7/token/ERC20/SafeERC20.sol"; import "@openzeppelin/contracts-v0.7/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts-v0.7/utils/ReentrancyGuard.sol"; import "./IRewardStaking.sol"; +import "./CvxMining.sol"; interface IBooster { function poolInfo(uint256 _pid) @@ -22,8 +23,6 @@ interface IBooster { address _stash, bool _shutdown ); - - function earmarkRewards(uint256 _pid) external returns (bool); } interface IConvexDeposits { @@ -40,13 +39,9 @@ interface IConvexDeposits { ) external; } -interface ITokenWrapper { - function token() external view returns (address); -} - // if used as collateral some modifications will be needed to fit the specific platform -// Based on audited contracts: https://github.com/convex-eth/platform/blob/933ace34d896e6684345c6795bf33d4089fbd8f6/contracts/contracts/wrappers/ConvexStakingWrapper.sol +// Based on audited contracts: https://github.com/convex-eth/platform/blob/main/contracts/contracts/wrappers/CvxCrvStakingWrapper.sol contract ConvexStakingWrapper is ERC20, ReentrancyGuard { using SafeERC20 for IERC20; using SafeMath for uint256; @@ -59,8 +54,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { struct RewardType { address reward_token; address reward_pool; - uint256 reward_integral; - uint256 reward_remaining; + uint128 reward_integral; + uint128 reward_remaining; mapping(address => uint256) reward_integral_for; mapping(address => uint256) claimable_reward; } @@ -80,10 +75,11 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { //rewards RewardType[] public rewards; mapping(address => uint256) public registeredRewards; - mapping(address => address) public rewardRedirect; //management bool public isInit; + address public owner; + bool internal _isShutdown; string internal _tokenname; string internal _tokensymbol; @@ -95,15 +91,15 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { bool _wrapped ); event Withdrawn(address indexed _user, uint256 _amount, bool _unwrapped); - event RewardRedirected(address indexed _account, address _forward); - event RewardAdded(address _token); - event UserCheckpoint(address _userA, address _userB); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); constructor() public ERC20("StakedConvexToken", "stkCvx") {} function initialize(uint256 _poolId) external virtual { require(!isInit, "already init"); + owner = msg.sender; + emit OwnershipTransferred(address(0), owner); (address _lptoken, address _token, , address _rewards, , ) = IBooster(convexBooster) .poolInfo(_poolId); @@ -135,6 +131,32 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { return 18; } + modifier onlyOwner() { + require(owner == msg.sender, "Ownable: caller is not the owner"); + _; + } + + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(owner, newOwner); + owner = newOwner; + } + + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(owner, address(0)); + owner = address(0); + } + + function shutdown() external onlyOwner { + _isShutdown = true; + } + + function isShutdown() public view returns (bool) { + if (_isShutdown) return true; + (, , , , , bool isShutdown_) = IBooster(convexBooster).poolInfo(convexPoolId); + return isShutdown_; + } + function setApprovals() public { IERC20(curveToken).safeApprove(convexBooster, 0); IERC20(curveToken).safeApprove(convexBooster, uint256(-1)); @@ -170,18 +192,12 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { //send to self to warmup state //slither-disable-next-line unchecked-transfer IERC20(cvx).transfer(address(this), 0); - emit RewardAdded(crv); - emit RewardAdded(cvx); } uint256 extraCount = IRewardStaking(mainPool).extraRewardsLength(); for (uint256 i = 0; i < extraCount; i++) { address extraPool = IRewardStaking(mainPool).extraRewards(i); address extraToken = IRewardStaking(extraPool).rewardToken(); - //from pool 151, extra reward tokens are wrapped - if (convexPoolId >= 151) { - extraToken = ITokenWrapper(extraToken).token(); - } if (extraToken == cvx) { //update cvx reward pool address rewards[CVX_INDEX].reward_pool = extraPool; @@ -189,14 +205,13 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { //add new token to list rewards.push( RewardType({ - reward_token: extraToken, + reward_token: IRewardStaking(extraPool).rewardToken(), reward_pool: extraPool, reward_integral: 0, reward_remaining: 0 }) ); registeredRewards[extraToken] = rewards.length; //mark registered at index+1 - emit RewardAdded(extraToken); } } } @@ -220,15 +235,6 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { return totalSupply(); } - //internal transfer function to transfer rewards out on claim - function _transferReward( - address _token, - address _to, - uint256 _amount - ) internal virtual { - IERC20(_token).safeTransfer(_to, _amount); - } - function _calcRewardIntegral( uint256 _index, address[2] memory _accounts, @@ -237,19 +243,16 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { bool _isClaim ) internal { RewardType storage reward = rewards[_index]; - if (reward.reward_token == address(0)) { - return; - } //get difference in balance and remaining rewards //getReward is unguarded so we use reward_remaining to keep track of how much was actually claimed uint256 bal = IERC20(reward.reward_token).balanceOf(address(this)); + // uint256 d_reward = bal.sub(reward.reward_remaining); - //check that balance increased and update integral - if (_supply > 0 && bal > reward.reward_remaining) { + if (_supply > 0 && bal.sub(reward.reward_remaining) > 0) { reward.reward_integral = reward.reward_integral + - (bal.sub(reward.reward_remaining).mul(1e20).div(_supply)); + uint128(bal.sub(reward.reward_remaining).mul(1e20).div(_supply)); } //update user integrals @@ -263,20 +266,20 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { if (_isClaim || userI < reward.reward_integral) { if (_isClaim) { uint256 receiveable = reward.claimable_reward[_accounts[u]].add( - _balances[u].mul(reward.reward_integral.sub(userI)).div(1e20) + _balances[u].mul(uint256(reward.reward_integral).sub(userI)).div(1e20) ); if (receiveable > 0) { reward.claimable_reward[_accounts[u]] = 0; //cheat for gas savings by transfering to the second index in accounts list //if claiming only the 0 index will update so 1 index can hold forwarding info //guaranteed to have an address in u+1 so no need to check - _transferReward(reward.reward_token, _accounts[u + 1], receiveable); + IERC20(reward.reward_token).safeTransfer(_accounts[u + 1], receiveable); bal = bal.sub(receiveable); } } else { reward.claimable_reward[_accounts[u]] = reward .claimable_reward[_accounts[u]] - .add(_balances[u].mul(reward.reward_integral.sub(userI)).div(1e20)); + .add(_balances[u].mul(uint256(reward.reward_integral).sub(userI)).div(1e20)); } reward.reward_integral_for[_accounts[u]] = reward.reward_integral; } @@ -284,7 +287,7 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { //update remaining reward here since balance could have changed if claiming if (bal != reward.reward_remaining) { - reward.reward_remaining = bal; + reward.reward_remaining = uint128(bal); } } @@ -294,13 +297,16 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { depositedBalance[0] = _getDepositedBalance(_accounts[0]); depositedBalance[1] = _getDepositedBalance(_accounts[1]); - IRewardStaking(convexPool).getReward(address(this), true); + if (!isShutdown()) { + IRewardStaking(convexPool).getReward(address(this), true); + } + + _claimExtras(); uint256 rewardCount = rewards.length; for (uint256 i = 0; i < rewardCount; i++) { _calcRewardIntegral(i, _accounts, depositedBalance, supply, false); } - emit UserCheckpoint(_accounts[0], _accounts[1]); } function _checkpointAndClaim(address[2] memory _accounts) internal nonReentrant { @@ -310,11 +316,17 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { IRewardStaking(convexPool).getReward(address(this), true); + _claimExtras(); + uint256 rewardCount = rewards.length; for (uint256 i = 0; i < rewardCount; i++) { _calcRewardIntegral(i, _accounts, depositedBalance, supply, true); } - emit UserCheckpoint(_accounts[0], _accounts[1]); + } + + //claim any rewards not part of the convex pool + function _claimExtras() internal virtual { + //override and add external reward claiming } function user_checkpoint(address _account) external returns (bool) { @@ -328,54 +340,81 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { //run earned as a mutable function to claim everything before calculating earned rewards function earned(address _account) external returns (EarnedData[] memory claimable) { - //checkpoint to pull in and tally new rewards - _checkpoint([_account, address(0)]); + IRewardStaking(convexPool).getReward(address(this), true); + _claimExtras(); + return _earned(_account); + } + + //run earned as a non-mutative function that may not claim everything, but should report standard convex rewards + function earnedView(address _account) external view returns (EarnedData[] memory claimable) { return _earned(_account); } function _earned(address _account) internal view returns (EarnedData[] memory claimable) { + uint256 supply = _getTotalSupply(); + // uint256 depositedBalance = _getDepositedBalance(_account); uint256 rewardCount = rewards.length; claimable = new EarnedData[](rewardCount); for (uint256 i = 0; i < rewardCount; i++) { RewardType storage reward = rewards[i]; - if (reward.reward_token == address(0)) { - continue; + + //change in reward is current balance - remaining reward + earned + uint256 bal = IERC20(reward.reward_token).balanceOf(address(this)); + uint256 d_reward = bal.sub(reward.reward_remaining); + + //some rewards (like minted cvx) may not have a reward pool directly on the convex pool so check if it exists + if (reward.reward_pool != address(0)) { + //add earned from the convex reward pool for the given token + d_reward = d_reward.add(IRewardStaking(reward.reward_pool).earned(address(this))); + } + + uint256 I = reward.reward_integral; + if (supply > 0) { + I = I + d_reward.mul(1e20).div(supply); } - claimable[i].amount = reward.claimable_reward[_account]; + uint256 newlyClaimable = _getDepositedBalance(_account) + .mul(I.sub(reward.reward_integral_for[_account])) + .div(1e20); + claimable[i].amount = claimable[i].amount.add( + reward.claimable_reward[_account].add(newlyClaimable) + ); claimable[i].token = reward.reward_token; + + //calc cvx minted from crv and add to cvx claimables + //note: crv is always index 0 so will always run before cvx + if (i == CRV_INDEX) { + //because someone can call claim for the pool outside of checkpoints, need to recalculate crv without the local balance + I = reward.reward_integral; + if (supply > 0) { + I = + I + + IRewardStaking(reward.reward_pool).earned(address(this)).mul(1e20).div( + supply + ); + } + newlyClaimable = _getDepositedBalance(_account) + .mul(I.sub(reward.reward_integral_for[_account])) + .div(1e20); + claimable[CVX_INDEX].amount = CvxMining.ConvertCrvToCvx(newlyClaimable); + claimable[CVX_INDEX].token = cvx; + } } return claimable; } function claimRewards() external { - address _account = rewardRedirect[msg.sender] == address(0) - ? msg.sender - : rewardRedirect[msg.sender]; - - uint256 cvxOldBal = IERC20(cvx).balanceOf(_account); - uint256 crvOldBal = IERC20(crv).balanceOf(_account); - _checkpointAndClaim([msg.sender, _account]); - emit RewardsClaimed(IERC20(cvx), IERC20(cvx).balanceOf(_account) - cvxOldBal); - emit RewardsClaimed(IERC20(crv), IERC20(crv).balanceOf(_account) - crvOldBal); - } - - //set any claimed rewards to automatically go to a different address - //set address to zero to disable - function setRewardRedirect(address _to) external nonReentrant { - rewardRedirect[msg.sender] = _to; - emit RewardRedirected(msg.sender, _to); + uint256 cvxOldBal = IERC20(cvx).balanceOf(msg.sender); + uint256 crvOldBal = IERC20(crv).balanceOf(msg.sender); + _checkpointAndClaim([address(msg.sender), address(msg.sender)]); + emit RewardsClaimed(IERC20(cvx), IERC20(cvx).balanceOf(msg.sender) - cvxOldBal); + emit RewardsClaimed(IERC20(crv), IERC20(crv).balanceOf(msg.sender) - crvOldBal); } function getReward(address _account) external { - //check if there is a redirect address - if (rewardRedirect[_account] != address(0)) { - _checkpointAndClaim([_account, rewardRedirect[_account]]); - } else { - //claim directly in checkpoint logic to save a bit of gas - _checkpointAndClaim([_account, _account]); - } + //claim directly in checkpoint logic to save a bit of gas + _checkpointAndClaim([_account, _account]); } function getReward(address _account, address _forwardTo) external { @@ -387,6 +426,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { //deposit a curve token function deposit(uint256 _amount, address _to) external { + require(!isShutdown(), "shutdown"); + //dont need to call checkpoint since _mint() will if (_amount > 0) { @@ -400,6 +441,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { //stake a convex token function stake(uint256 _amount, address _to) external { + require(!isShutdown(), "shutdown"); + //dont need to call checkpoint since _mint() will if (_amount > 0) { @@ -445,10 +488,5 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { ) internal override { _checkpoint([_from, _to]); } - - //helper function - function earmarkRewards() external returns (bool) { - return IBooster(convexBooster).earmarkRewards(convexPoolId); - } } // slither-disable-end reentrancy-no-eth \ No newline at end of file diff --git a/contracts/plugins/assets/dsr/SDaiCollateral.sol b/contracts/plugins/assets/dsr/SDaiCollateral.sol index 5401b2ad5f..8e7643575f 100644 --- a/contracts/plugins/assets/dsr/SDaiCollateral.sol +++ b/contracts/plugins/assets/dsr/SDaiCollateral.sol @@ -35,7 +35,6 @@ 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 ed741e15ec..58fd23855c 100644 --- a/contracts/plugins/assets/erc20/RewardableERC20.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20.sol @@ -7,14 +7,11 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../../interfaces/IRewardable.sol"; -uint256 constant SHARE_DECIMAL_OFFSET = 9; // to prevent reward rounding issues - /** * @title RewardableERC20 * @notice An abstract class that can be extended to create rewardable wrapper. * @notice `_claimAssetRewards` keeps tracks of rewards by snapshotting the balance * and calculating the difference between the current balance and the previous balance. - * Limitation: Currently supports only one single reward token. * @dev To inherit: * - override _claimAssetRewards() * - call ERC20 constructor elsewhere during construction @@ -22,11 +19,11 @@ uint256 constant SHARE_DECIMAL_OFFSET = 9; // to prevent reward rounding issues abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { using SafeERC20 for IERC20; - uint256 public immutable one; // 1e9 * {qShare/share} + uint256 public immutable one; // {qShare/share} IERC20 public immutable rewardToken; - uint256 public rewardsPerShare; // 1e9 * {qRewards/share} - mapping(address => uint256) public lastRewardsPerShare; // 1e9 * {qRewards/share} + uint256 public rewardsPerShare; // {qRewards/share} + mapping(address => uint256) public lastRewardsPerShare; // {qRewards/share} mapping(address => uint256) public accumulatedRewards; // {qRewards} mapping(address => uint256) public claimedRewards; // {qRewards} @@ -38,11 +35,9 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { /// @dev Extending class must ensure ERC20 constructor is called constructor(IERC20 _rewardToken, uint8 _decimals) { rewardToken = _rewardToken; - // set via pass-in to prevent inheritance issues - one = 10**(_decimals + SHARE_DECIMAL_OFFSET); + one = 10**_decimals; // set via pass-in to prevent inheritance issues } - // claim rewards - Only supports one single reward token function claimRewards() external nonReentrant { _claimAndSyncRewards(); _syncAccount(msg.sender); @@ -52,7 +47,7 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { function _syncAccount(address account) internal { if (account == address(0)) return; - // 1e9 * {qRewards/share} + // {qRewards/share} uint256 accountRewardsPerShare = lastRewardsPerShare[account]; // {qShare} @@ -61,48 +56,37 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { // {qRewards} uint256 _accumulatedRewards = accumulatedRewards[account]; - // 1e9 * {qRewards/share} + // {qRewards/share} uint256 _rewardsPerShare = rewardsPerShare; if (accountRewardsPerShare < _rewardsPerShare) { - // 1e9 * {qRewards/share} + // {qRewards/share} uint256 delta = _rewardsPerShare - accountRewardsPerShare; - // {qRewards} = (1e9 * {qRewards/share}) * {qShare} / (1e9 * {qShare/share}) + // {qRewards} = {qRewards/share} * {qShare} _accumulatedRewards += (delta * shares) / one; } lastRewardsPerShare[account] = _rewardsPerShare; accumulatedRewards[account] = _accumulatedRewards; } - function _rewardTokenBalance() internal view virtual returns (uint256) { - return rewardToken.balanceOf(address(this)); - } - - function _distributeReward(address account, uint256 amt) internal virtual { - rewardToken.safeTransfer(account, amt); - } - function _claimAndSyncRewards() internal virtual { uint256 _totalSupply = totalSupply(); if (_totalSupply == 0) { return; } _claimAssetRewards(); - uint256 balanceAfterClaimingRewards = _rewardTokenBalance(); + uint256 balanceAfterClaimingRewards = rewardToken.balanceOf(address(this)); uint256 _rewardsPerShare = rewardsPerShare; uint256 _previousBalance = lastRewardBalance; if (balanceAfterClaimingRewards > _previousBalance) { - uint256 delta = balanceAfterClaimingRewards - _previousBalance; // {qRewards} - - // 1e9 * {qRewards/share} = {qRewards} * (1e9 * {qShare/share}) / {qShare} + uint256 delta = balanceAfterClaimingRewards - _previousBalance; uint256 deltaPerShare = (delta * one) / _totalSupply; - // {qRewards} = {qRewards} + (1e9*(qRewards/share)) * {qShare} / (1e9*{qShare/share}) balanceAfterClaimingRewards = _previousBalance + (deltaPerShare * _totalSupply) / one; - // 1e9 * {qRewards/share} += {qRewards} * (1e9*{qShare/share}) / {qShare} + // {qRewards/share} += {qRewards} * {qShare/share} / {qShare} _rewardsPerShare += deltaPerShare; } @@ -121,7 +105,7 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { claimedRewards[account] = accumulatedRewards[account]; - uint256 currentRewardTokenBalance = _rewardTokenBalance(); + uint256 currentRewardTokenBalance = rewardToken.balanceOf(address(this)); // This is just to handle the edge case where totalSupply() == 0 and there // are still reward tokens in the contract. @@ -129,9 +113,9 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { ? currentRewardTokenBalance - lastRewardBalance : 0; - _distributeReward(account, claimableRewards); + rewardToken.safeTransfer(account, claimableRewards); - currentRewardTokenBalance = _rewardTokenBalance(); + currentRewardTokenBalance = rewardToken.balanceOf(address(this)); lastRewardBalance = currentRewardTokenBalance > nonDistributed ? currentRewardTokenBalance - nonDistributed : 0; diff --git a/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol index 6ae34a21a8..e2a4ec927f 100644 --- a/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol @@ -30,10 +30,6 @@ 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 3966e66ea9..284f717c2e 100644 --- a/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol +++ b/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol @@ -27,9 +27,7 @@ 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/README.md b/contracts/plugins/assets/frax-eth/README.md index 7d32cc254a..4e95c0a05a 100644 --- a/contracts/plugins/assets/frax-eth/README.md +++ b/contracts/plugins/assets/frax-eth/README.md @@ -34,4 +34,4 @@ This function returns rate of `frxETH/sfrxETH`, getting from [pricePerShare()](h #### tryPrice -This function uses `refPerTok` and the chainlink price of `USD/ETH` to return the current price range of the collateral. Once an oracle becomes available for `frxETH/ETH`, this function should be modified to use it and return the appropiate `pegPrice`. +This function uses `refPerTok`, the chainlink price of `ETH/frxETH`, and the chainlink price of `USD/ETH` to return the current price range of the collateral. diff --git a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol index c3dbe91379..4697ec0da0 100644 --- a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol +++ b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol @@ -7,12 +7,6 @@ import "../AppreciatingFiatCollateral.sol"; import "../OracleLib.sol"; import "./vendor/IsfrxEth.sol"; -/** - * ************************************************************ - * WARNING: this plugin is not ready to be used in Production - * ************************************************************ - */ - /** * @title SFraxEthCollateral * @notice Collateral plugin for Frax-ETH, @@ -29,16 +23,14 @@ 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 /// Can revert, used by other contract functions in order to catch errors /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate - /// @return pegPrice {target/ref} FIX_ONE until an oracle becomes available + /// @return pegPrice {target/ref} The actual price observed in the peg function tryPrice() external view @@ -57,8 +49,6 @@ contract SFraxEthCollateral is AppreciatingFiatCollateral { high = p + err; // assert(low <= high); obviously true just by inspection - // TODO: Currently not checking for depegs between `frxETH` and `ETH` - // Should be modified to use a `frxETH/ETH` oracle when available pegPrice = targetPerRef(); } diff --git a/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol b/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol index 9267f40e76..783896f2c0 100644 --- a/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol +++ b/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol @@ -35,8 +35,6 @@ 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/MorphoAaveV2TokenisedDeposit.sol b/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol index bc5f32abd8..eff7ccd9b5 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol @@ -3,12 +3,13 @@ pragma solidity 0.8.19; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; -import { IMorpho, IMorphoUsersLens } from "./IMorpho.sol"; +import { IMorpho, IMorphoRewardsDistributor, IMorphoUsersLens } from "./IMorpho.sol"; import { MorphoTokenisedDeposit, MorphoTokenisedDepositConfig } from "./MorphoTokenisedDeposit.sol"; struct MorphoAaveV2TokenisedDepositConfig { IMorpho morphoController; IMorphoUsersLens morphoLens; + IMorphoRewardsDistributor rewardsDistributor; IERC20Metadata underlyingERC20; IERC20Metadata poolToken; ERC20 rewardToken; @@ -21,6 +22,7 @@ contract MorphoAaveV2TokenisedDeposit is MorphoTokenisedDeposit { MorphoTokenisedDeposit( MorphoTokenisedDepositConfig({ morphoController: config.morphoController, + rewardsDistributor: config.rewardsDistributor, underlyingERC20: config.underlyingERC20, poolToken: config.poolToken, rewardToken: config.rewardToken diff --git a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol index 248c24084c..5959b944ec 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol @@ -28,7 +28,6 @@ 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/morpho-aave/MorphoNonFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol index 3f1fe73110..27449c2883 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol @@ -16,13 +16,13 @@ contract MorphoNonFiatCollateral is MorphoFiatCollateral { using OracleLib for AggregatorV3Interface; using FixLib for uint192; - AggregatorV3Interface public immutable targetUnitChainlinkFeed; // {UoA/target} + AggregatorV3Interface public immutable targetUnitChainlinkFeed; // {target/ref} uint48 public immutable targetUnitOracleTimeout; // {s} /// @dev config.erc20 must be a MorphoTokenisedDeposit - /// @param config.chainlinkFeed Feed units: {target/ref} + /// @param config.chainlinkFeed Feed units: {UoA/target} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide - /// @param targetUnitChainlinkFeed_ Feed units: {UoA/target} + /// @param targetUnitChainlinkFeed_ Feed units: {target/ref} /// @param targetUnitOracleTimeout_ {s} oracle timeout to use for targetUnitChainlinkFeed constructor( CollateralConfig memory config, @@ -48,12 +48,11 @@ contract MorphoNonFiatCollateral is MorphoFiatCollateral { uint192 pegPrice ) { - pegPrice = chainlinkFeed.price(oracleTimeout); // {target/ref} + // {tar/ref} Get current market peg + pegPrice = targetUnitChainlinkFeed.price(targetUnitOracleTimeout); // {UoA/tok} = {UoA/target} * {target/ref} * {ref/tok} - uint192 p = targetUnitChainlinkFeed.price(targetUnitOracleTimeout).mul(pegPrice).mul( - _underlyingRefPerTok() - ); + uint192 p = chainlinkFeed.price(oracleTimeout).mul(pegPrice).mul(_underlyingRefPerTok()); uint192 err = p.mul(oracleError, CEIL); high = p + err; diff --git a/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol b/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol index e2bf558fe5..d2664e782c 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol @@ -3,34 +3,23 @@ pragma solidity 0.8.19; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; -import { IMorpho, IMorphoUsersLens } from "./IMorpho.sol"; +import { IMorpho, IMorphoRewardsDistributor, IMorphoUsersLens } from "./IMorpho.sol"; import { RewardableERC4626Vault } from "../erc20/RewardableERC4626Vault.sol"; struct MorphoTokenisedDepositConfig { IMorpho morphoController; + IMorphoRewardsDistributor rewardsDistributor; IERC20Metadata underlyingERC20; IERC20Metadata poolToken; ERC20 rewardToken; } abstract contract MorphoTokenisedDeposit is RewardableERC4626Vault { - struct MorphoTokenisedDepositRewardsAccountingState { - uint256 totalAccumulatedBalance; - uint256 totalPaidOutBalance; - uint256 pendingBalance; - uint256 availableBalance; - uint256 remainingPeriod; - uint256 lastSync; - } - - uint256 private constant PAYOUT_PERIOD = 7 days; - + IMorphoRewardsDistributor public immutable rewardsDistributor; IMorpho public immutable morphoController; address public immutable poolToken; address public immutable underlying; - MorphoTokenisedDepositRewardsAccountingState private state; - constructor(MorphoTokenisedDepositConfig memory config) RewardableERC4626Vault( config.underlyingERC20, @@ -42,53 +31,17 @@ abstract contract MorphoTokenisedDeposit is RewardableERC4626Vault { underlying = address(config.underlyingERC20); morphoController = config.morphoController; poolToken = address(config.poolToken); - state.lastSync = uint48(block.timestamp); + rewardsDistributor = config.rewardsDistributor; } - function sync() external { + function rewardTokenBalance(address account) external returns (uint256 claimableRewards) { _claimAndSyncRewards(); + _syncAccount(account); + claimableRewards = accumulatedRewards[account] - claimedRewards[account]; } - function _claimAssetRewards() internal override { - // If we detect any new balances add it to pending and reset payout period - uint256 totalAccumulated = state.totalPaidOutBalance + rewardToken.balanceOf(address(this)); - uint256 newlyAccumulated = totalAccumulated - state.totalAccumulatedBalance; - - uint256 timeDelta = block.timestamp - state.lastSync; - if (timeDelta != 0 && state.remainingPeriod != 0) { - if (timeDelta > state.remainingPeriod) { - timeDelta = state.remainingPeriod; - } - - uint256 amtToPayOut = (state.pendingBalance * timeDelta) / state.remainingPeriod; - state.pendingBalance -= amtToPayOut; - state.availableBalance += amtToPayOut; - } - - if (newlyAccumulated != 0) { - state.totalAccumulatedBalance = totalAccumulated; - state.pendingBalance += newlyAccumulated; - - state.remainingPeriod = PAYOUT_PERIOD; - } else { - state.remainingPeriod = state.remainingPeriod < timeDelta - ? 0 - : state.remainingPeriod - timeDelta; - } - - state.lastSync = block.timestamp; - } - - function _rewardTokenBalance() internal view override returns (uint256) { - return state.availableBalance; - } - - function _distributeReward(address account, uint256 amt) internal override { - state.totalPaidOutBalance += amt; - state.availableBalance -= amt; - - SafeERC20.safeTransfer(rewardToken, account, amt); - } + // solhint-disable-next-line no-empty-blocks + function _claimAssetRewards() internal virtual override {} function getMorphoPoolBalance(address poolToken) internal view virtual returns (uint256); diff --git a/contracts/plugins/assets/rocket-eth/RethCollateral.sol b/contracts/plugins/assets/rocket-eth/RethCollateral.sol index 97c58aaef4..f7f4386650 100644 --- a/contracts/plugins/assets/rocket-eth/RethCollateral.sol +++ b/contracts/plugins/assets/rocket-eth/RethCollateral.sol @@ -31,7 +31,6 @@ 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 d31d7b04df..8aaf4b2381 100644 --- a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol +++ b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol @@ -22,7 +22,6 @@ 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/assets/yearnv2/YearnV2CurveFiatCollateral.sol b/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol index 322eca9a75..9121982562 100644 --- a/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol +++ b/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol @@ -3,11 +3,9 @@ pragma solidity 0.8.19; import "../curve/CurveStableCollateral.sol"; -interface IPricePerShareHelper { - /// @param vault The yToken address - /// @param amount {qTok} - /// @return {qLP Token} - function amountToShares(address vault, uint256 amount) external view returns (uint256); +interface IYearnV2 { + /// @return {qLP token/tok} + function pricePerShare() external view returns (uint256); } /** @@ -18,27 +16,28 @@ interface IPricePerShareHelper { * tar = USD * UoA = USD * - * More on the ref token: crvUSDUSDC-f has a virtual price. The ref token to measure is not the + * More on the ref token: crvUSDUSDC-f has a virtual price >=1. The ref token to measure is not the * balance of crvUSDUSDC-f that the LP token is redeemable for, but the balance of the virtual * token that underlies crvUSDUSDC-f. This virtual token is an evolving mix of USDC and crvUSD. * - * Should only be used for Stable pools. - * No rewards (handled internally by the Yearn vault). - * Revenue hiding can be kept very small since stable curve pools should be up-only. + * Revenue hiding should be set to the largest % drawdown in a Yearn vault that should + * not result in default. While it is extremely rare for Yearn to have drawdowns, + * in principle it is possible and should be planned for. + * + * No rewards. */ contract YearnV2CurveFiatCollateral is CurveStableCollateral { using FixLib for uint192; - IPricePerShareHelper public immutable pricePerShareHelper; + // solhint-disable no-empty-blocks constructor( CollateralConfig memory config, uint192 revenueHiding, - PTConfiguration memory ptConfig, - IPricePerShareHelper pricePerShareHelper_ - ) CurveStableCollateral(config, revenueHiding, ptConfig) { - pricePerShareHelper = pricePerShareHelper_; - } + PTConfiguration memory ptConfig + ) CurveStableCollateral(config, revenueHiding, ptConfig) {} + + // solhint-enable no-empty-blocks /// Can revert, used by other contract functions in order to catch errors /// Should not return FIX_MAX for low @@ -92,18 +91,12 @@ contract YearnV2CurveFiatCollateral is CurveStableCollateral { /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view virtual override returns (uint192) { // {ref/tok} = {ref/LP token} * {LP token/tok} - return _safeWrap(curvePool.get_virtual_price()).mul(_pricePerShare(), FLOOR); + return _safeWrap(curvePool.get_virtual_price()).mul(_pricePerShare()); } /// @return {LP token/tok} function _pricePerShare() internal view returns (uint192) { - uint256 supply = erc20.totalSupply(); // {qTok} - uint256 shares = pricePerShareHelper.amountToShares(address(erc20), supply); // {qLP Token} - - // yvCurve tokens always have the same number of decimals as the underlying curve LP token, - // so we can divide the quanta units without converting to whole units - - // {LP token/tok} = {LP token} / {tok} - return divuu(shares, supply); + // {LP token/tok} = {qLP token/tok} * {LP token/qLP token} + return shiftl_toFix(IYearnV2(address(erc20)).pricePerShare(), -int8(erc20Decimals)); } } diff --git a/contracts/plugins/governance/Governance.sol b/contracts/plugins/governance/Governance.sol index 96de231912..c20978fa02 100644 --- a/contracts/plugins/governance/Governance.sol +++ b/contracts/plugins/governance/Governance.sol @@ -8,9 +8,6 @@ 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 @@ -33,9 +30,7 @@ contract Governance is // 100% uint256 public constant ONE_HUNDRED_PERCENT = 1e8; // {micro %} - // solhint-disable-next-line var-name-mixedcase - uint256 public immutable MIN_VOTING_DELAY; // {block} equal to ONE_DAY - + // solhint-disable no-empty-blocks constructor( IStRSRVotes token_, TimelockController timelock_, @@ -49,12 +44,7 @@ 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 @@ -66,11 +56,6 @@ 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 @@ -190,8 +175,4 @@ 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 0396a5ea35..b6abe6fffb 100644 --- a/contracts/plugins/mocks/AssetMock.sol +++ b/contracts/plugins/mocks/AssetMock.sol @@ -4,8 +4,6 @@ pragma solidity 0.8.19; import "../assets/Asset.sol"; contract AssetMock is Asset { - bool public stale; - uint192 private lowPrice; uint192 private highPrice; @@ -14,7 +12,7 @@ contract AssetMock is Asset { /// @param oracleError_ {1} The % the oracle feed can be off by /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA /// @param oracleTimeout_ {s} The number of seconds until a oracle value becomes invalid - /// @dev oracleTimeout_ is also used as the timeout value in price(), should be highest of + /// @dev oracleTimeout_ is also used as the timeout value in lotPrice(), should be highest of /// all assets' oracleTimeout in a collateral if there are multiple oracles constructor( uint48 priceTimeout_, @@ -42,18 +40,13 @@ contract AssetMock is Asset { uint192 ) { - require(!stale, "stale price"); return (lowPrice, highPrice, 0); } /// Should not revert /// Refresh saved prices function refresh() public virtual override { - stale = false; - } - - function setStale(bool _stale) external { - stale = _stale; + // pass } function setPrice(uint192 low, uint192 high) external { diff --git a/contracts/plugins/mocks/CTokenWrapperMock.sol b/contracts/plugins/mocks/CTokenWrapperMock.sol index 78a93b44af..c0cd0922a6 100644 --- a/contracts/plugins/mocks/CTokenWrapperMock.sol +++ b/contracts/plugins/mocks/CTokenWrapperMock.sol @@ -42,11 +42,9 @@ contract CTokenWrapperMock is ERC20Mock, IRewardable { revert("reverting claim rewards"); } uint256 oldBal = comp.balanceOf(msg.sender); - address[] memory holders = new address[](1); address[] memory cTokens = new address[](1); - holders[0] = msg.sender; cTokens[0] = address(underlying); - comptroller.claimComp(holders, cTokens, false, true); + comptroller.claimComp(msg.sender, cTokens); emit RewardsClaimed(IERC20(address(comp)), comp.balanceOf(msg.sender) - oldBal); } diff --git a/contracts/plugins/mocks/ComptrollerMock.sol b/contracts/plugins/mocks/ComptrollerMock.sol index 249bcdb088..9f95726479 100644 --- a/contracts/plugins/mocks/ComptrollerMock.sol +++ b/contracts/plugins/mocks/ComptrollerMock.sol @@ -19,14 +19,8 @@ contract ComptrollerMock is IComptroller { compBalances[recipient] = amount; } - function claimComp( - address[] memory holders, - address[] memory, - bool, - bool - ) external { + function claimComp(address holder, address[] memory) external { // Mint amount and update internal balances - address holder = holders[0]; if (address(compToken) != address(0)) { uint256 amount = compBalances[holder]; compBalances[holder] = 0; @@ -37,9 +31,4 @@ contract ComptrollerMock is IComptroller { function getCompAddress() external view returns (address) { return address(compToken); } - - // mock - function enterMarkets(address[] calldata) external returns (uint256[] memory) { - return new uint256[](1); - } } diff --git a/contracts/plugins/mocks/RevenueTraderBackComp.sol b/contracts/plugins/mocks/RevenueTraderBackComp.sol index 73069f15ad..ed76f53346 100644 --- a/contracts/plugins/mocks/RevenueTraderBackComp.sol +++ b/contracts/plugins/mocks/RevenueTraderBackComp.sol @@ -14,10 +14,8 @@ 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) - // solhint-disable-next-line no-empty-blocks - try this.manageTokens(erc20s, kinds) {} catch {} + this.manageTokens(erc20s, kinds); } function version() public pure virtual override(Versioned, IVersioned) returns (string memory) { diff --git a/contracts/plugins/mocks/upgrades/FacadeMonitorV2.sol b/contracts/plugins/mocks/upgrades/FacadeMonitorV2.sol deleted file mode 100644 index ebbfc6b1c2..0000000000 --- a/contracts/plugins/mocks/upgrades/FacadeMonitorV2.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -import "../../../facade/FacadeMonitor.sol"; - -/** - * @title FacadeMonitorV2 - * @notice Mock to test upgradeability for the FacadeMonitor contract - */ -contract FacadeMonitorV2 is FacadeMonitor { - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(MonitorParams memory params) FacadeMonitor(params) {} - - uint256 public newValue; - - function setNewValue(uint256 newValue_) external onlyOwner { - newValue = newValue_; - } - - function version() public pure returns (string memory) { - return "2.0.0"; - } -} diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index c494ecee57..9f52e6387a 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -43,8 +43,7 @@ contract GnosisTrade is ITrade { address public origin; IERC20Metadata public sell; // address of token this trade is selling IERC20Metadata public buy; // address of token this trade is buying - uint256 public initBal; // {qSellTok}, this trade's balance of `sell` when init() was called - uint192 public sellAmount; // {sellTok}, quantity of whole tokens being sold; dup with initBal + uint256 public initBal; // {qTok}, this trade's balance of `sell` when init() was called uint48 public endTime; // timestamp after which this trade's auction can be settled uint192 public worstCasePrice; // {buyTok/sellTok}, the worst price we expect to get at Auction // We expect Gnosis Auction either to meet or beat worstCasePrice, or to return the `sell` @@ -90,8 +89,7 @@ contract GnosisTrade is ITrade { sell = req.sell.erc20(); buy = req.buy.erc20(); - initBal = sell.balanceOf(address(this)); // {qSellTok} - sellAmount = shiftl_toFix(initBal, -int8(sell.decimals())); // {sellTok} + initBal = sell.balanceOf(address(this)); require(initBal <= type(uint96).max, "initBal too large"); require(initBal >= req.sellAmount, "unfunded trade"); @@ -109,8 +107,8 @@ contract GnosisTrade is ITrade { ); // Downsize our sell amount to adjust for fee - // {qSellTok} = {qSellTok} * {1} / {1} - uint96 _sellAmount = uint96( + // {qTok} = {qTok} * {1} / {1} + uint96 sellAmount = uint96( _divrnd( req.sellAmount * FEE_DENOMINATOR, FEE_DENOMINATOR + gnosis.feeNumerator(), @@ -145,7 +143,7 @@ contract GnosisTrade is ITrade { buy, endTime, endTime, - _sellAmount, + sellAmount, minBuyAmount, minBuyAmtPerOrder, 0, diff --git a/docs/collateral.md b/docs/collateral.md index e6ae0e039c..7c95883f58 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -42,14 +42,12 @@ 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 - /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility /// @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); @@ -221,7 +219,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` 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. +When implementing Revenue Hiding, the `price/lotPrice()` functions should NOT hide revenue; they should use the current underlying exchange rate to calculate a best-effort estimate of what the collateral will trade at on secondary markets. A side-effect of this approach is that the RToken's price on markets becomes more variable. ## Important Properties for Collateral Plugins @@ -254,7 +252,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()`/`refresh()`. Extenders of this contract should not have to override any of these three functions, just `tryPrice()`. +To prevent `refresh()` from reverting due to overflow or other numeric errors, the base collateral plugin [Fiat Collateral](../contracts/plugins/assets/FiatCollateral.sol) has a `tryPrice()` function that encapsulates both the oracle lookup as well as any subsequent math required. This function is always executed via a try-catch in `price()`/`lotPrice()`/`refresh()`. Extenders of this contract should not have to override any of these three functions, just `tryPrice()`. ### The `IFFY` status should be temporary. @@ -301,7 +299,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()`/`refresh()`) +- `tryPrice()` (not on the ICollateral interface; used by `price()`/`lotPrice()`/`refresh()`) - `refPerTok()` - `targetPerRef()` @@ -364,21 +362,23 @@ Should never revert. Should return the tightest possible lower and upper estimate for the price of the token on secondary markets. -The difference between the upper and lower estimate should not exceed 5%, though this is not a hard-and-fast rule. When the difference (usually arising from an oracleError) is large, it can lead to [the price estimation of the RToken](../contracts/plugins/assets/RTokenAsset.sol) somewhat degrading. While this is not usually an issue it can come into play when one RToken is using another RToken as collateral either directly or indirectly through an LP token. If there is RSR overcollateralization then this issue is mitigated. - Lower estimate must be <= upper estimate. -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 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}` -Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility. +Should never revert. + +Lower estimate must be <= upper estimate. + +The low estimate should be nonzero while the asset is worth selling. -Recommend implement `lotPrice()` by calling `price()`. If you are inheriting from any of our existing collateral plugins, this is already done for you. See [Asset.sol](../contracts/plugins/Asset.sol) for the implementation. +Should be gas-efficient. ### refPerTok() `{ref/tok}` diff --git a/docs/deployed-addresses/1-FacadeMonitor.md b/docs/deployed-addresses/1-FacadeMonitor.md deleted file mode 100644 index e8cf1a8c05..0000000000 --- a/docs/deployed-addresses/1-FacadeMonitor.md +++ /dev/null @@ -1,7 +0,0 @@ -# FacadeMonitor (Mainnet) - -## Facade Monitor Proxy - -| Contract | Address | -| -------- | --------------------------------------------------------------------------------------------------------------------- | -| FacadeMonitor (Proxy) | [0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09](https://etherscan.io/address/0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09) | diff --git a/docs/deployed-addresses/8453-FacadeMonitor.md b/docs/deployed-addresses/8453-FacadeMonitor.md deleted file mode 100644 index 4cba0e181a..0000000000 --- a/docs/deployed-addresses/8453-FacadeMonitor.md +++ /dev/null @@ -1,8 +0,0 @@ -8453-FacadeMonitor.md -# FacadeMonitor (Base) - -## Facade Monitor Proxy - -| Contract | Address | -| --------------------- | --------------------------------------------------------------------------------------------------------------------- | -| FacadeMonitor (Proxy) | [0x5bfc6df700ef23741B2e01Bd45826E4c9735ae60](https://basescan.org/address/0x5bfc6df700ef23741B2e01Bd45826E4c9735ae60) | diff --git a/docs/deployment.md b/docs/deployment.md index 98c96678ef..6fbf565027 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: +- For each asset, confirm `lotPrice()` and `price()` are close. - `main.tradingPaused()` and `main.issuancePaused()` are true - `timelockController.minDelay()` is > 1e12 diff --git a/docs/exhaustive-tests.md b/docs/exhaustive-tests.md index 5fafdb48f3..fef7f481e0 100644 --- a/docs/exhaustive-tests.md +++ b/docs/exhaustive-tests.md @@ -1,6 +1,6 @@ # Exhaustive Testing -The exhaustive tests include `Broker.test.ts`, `Furnace.test.ts`, `RToken.test.ts`, `ZTradingExtremes.test.ts` and `ZZStRSR.test.ts`, and are meant to test the protocol when given permutations of input values on the extreme ends of the spectrum of possiblities. +The exhaustive tests include `Furnace.test.ts`, `RToken.test.ts`, `ZTradingExteremes.test.ts` and `ZZStRSR.test.ts`, and are meant to test the protocol when given permutations of input values on the extreme ends of the spectrum of possiblities. The env vars related to exhaustive testing are `EXTREME` and `SLOW`. @@ -12,7 +12,7 @@ I'm assuming you've already got `gcloud` installed on your dev machine. If not, ```bash gcloud auth login -gcloud config set project rtoken-testing +gcloud config set project rtoken-fuzz gcloud config list project # assumed defaults @@ -39,7 +39,7 @@ gcloud compute config-ssh Jump onto the instance: ``` -ssh exhaustive.us-central1-a.rtoken-testing +ssh exhaustive.us-central1-a.rtoken-fuzz ``` Add Matt's special seasoning, for tmux and emacs QoL improvements (NOTE: This sets the tmux `ctrl-b` to `ctrl-z`): @@ -93,7 +93,7 @@ gcloud compute config-ssh Jump onto the instance: ``` -ssh exhaustive.us-central1-a.rtoken-testing +ssh exhaustive.us-central1-a.rtoken-fuzz ``` ## 3) Run the tests @@ -113,7 +113,7 @@ Tmux and run the tests: ``` tmux -bash ./scripts/exhaustive-tests/run-exhaustive-tests.sh +bash ./scripts/run-exhaustive-tests.sh ``` When the test are complete, you'll find the console output in `tmux-1.log` and `tmux-2.log`. diff --git a/docs/monitoring.md b/docs/monitoring.md deleted file mode 100644 index df1c5f8baf..0000000000 --- a/docs/monitoring.md +++ /dev/null @@ -1,35 +0,0 @@ -# Monitoring the Reserve Protocol and Rtokens - -This document provides an overview of the monitoring setup for the Reserve Protocol and RTokens on both the Ethereum and Base networks. The monitoring is conducted through the [Hypernative](https://app.hypernative.xyz/) platform, utilizing the `FacadeMonitor` contract to retrieve the status for specific RTokens. This monitoring setup ensures continuous vigilance over the Reserve Protocol and RTokens, with alerts promptly notifying relevant channels in case of any issues. - -## Checks/Alerts - -The following alerts are currently setup for RTokens deployed in Mainnet and Base: - -### Status (Basket Handler) - HIGH - -Checks if the status of the Basket Handler for a specific RToken is SOUND. If not, triggers an alert via Slack, Discord, Telegram, and Pager Duty. - -### Fully collateralized (Basket Handler) - HIGH - -Checks if the Basket Handler for a specific RToken is FULLY COLLATERALIZED. If not, triggers an alert via Slack, Discord, Telegram, and Pager Duty. - -### Batch Auctions Disabled - HIGH - -Checks if the batch auctions for a specific RToken are DISABLED. If true, triggers an alert via Slack, Discord, Telegram, and Pager Duty. - -### Dutch Auctions Disabled - HIGH - -Checks if the any of the dutch auctions for a specific RToken is DISABLED. If true, triggers an alert via Slack, Discord, Telegram, and Pager Duty. - -### Issuance Depleted - MEDIUM - -Triggers and alert via Slack if the Issuance Throttle for a specific RToken is consumed > 99% - -### Redemption Depleted - MEDIUM - -Triggers and alert via Slack if the Redemption Throttle for a specific RToken is consumed > 99% - -### Backing Fully Redeemable- MEDIUM - -Triggers and alert via Slack if the backing of a specific RToken is not redeemable 100% on the underlying Defi Protocol. Provides checks for AAVE V2, AAVE V3, Compound V2, Compound V3, Stargate, Flux, and Morpho AAVE V2. diff --git a/docs/pause-freeze-states.md b/docs/pause-freeze-states.md deleted file mode 100644 index 17b2785fcd..0000000000 --- a/docs/pause-freeze-states.md +++ /dev/null @@ -1,73 +0,0 @@ -# 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 06cf836594..aecb345c94 100644 --- a/docs/recollateralization.md +++ b/docs/recollateralization.md @@ -64,7 +64,21 @@ If there does not exist a trade that meets these constraints, then the protocol #### Trade Sizing -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`. +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`. #### Trade Examples diff --git a/scripts/addresses/84531-RTKN-tmp-deployments.json b/scripts/addresses/84531-RTKN-tmp-deployments.json deleted file mode 100644 index e7ec7dc68f..0000000000 --- a/scripts/addresses/84531-RTKN-tmp-deployments.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "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 b5ea27b8ec..0c62bfbac1 100644 --- a/scripts/confirmation/1_confirm_assets.ts +++ b/scripts/confirmation/1_confirm_assets.ts @@ -2,8 +2,7 @@ import hre from 'hardhat' import { getChainId } from '../../common/blockchain-utils' import { developmentChains, networkConfig } from '../../common/configuration' -import { CollateralStatus, MAX_UINT192 } from '../../common/constants' -import { getLatestBlockTimestamp } from '#/utils/time' +import { CollateralStatus } from '../../common/constants' import { getDeploymentFile, IAssetCollDeployments, @@ -28,20 +27,18 @@ 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} - 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') + 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') + } } // Collateral @@ -52,18 +49,14 @@ 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} - 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') - } + 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') + } } } diff --git a/scripts/deploy.ts b/scripts/deploy.ts index e2916e7d00..eb9d82f713 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -62,8 +62,6 @@ async function main() { 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', 'phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts', 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts', - 'phase2-assets/collaterals/deploy_yearn_v2_curve_usdc.ts', - 'phase2-assets/collaterals/deploy_yearn_v2_curve_usdp.ts', 'phase2-assets/collaterals/deploy_sfrax.ts' ) } else if (chainId == '8453' || chainId == '84531') { diff --git a/scripts/deployment/phase1-common/1_deploy_libraries.ts b/scripts/deployment/phase1-common/1_deploy_libraries.ts index 78a4efa683..35fc34e373 100644 --- a/scripts/deployment/phase1-common/1_deploy_libraries.ts +++ b/scripts/deployment/phase1-common/1_deploy_libraries.ts @@ -8,6 +8,7 @@ import { BasketLibP1, CvxMining, RecollateralizationLibP1 } from '../../../typec let tradingLib: RecollateralizationLibP1 let basketLib: BasketLibP1 +let cvxMiningLib: CvxMining async function main() { // ==== Read Configuration ==== @@ -15,7 +16,7 @@ async function main() { const chainId = await getChainId(hre) console.log( - `Deploying TradingLib, BasketLib to network ${hre.network.name} (${chainId}) with burner account: ${burner.address}` + `Deploying TradingLib, BasketLib, and CvxMining to network ${hre.network.name} (${chainId}) with burner account: ${burner.address}` ) if (!networkConfig[chainId]) { @@ -45,9 +46,20 @@ async function main() { fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + // Deploy CvxMining external library + if (!baseL2Chains.includes(hre.network.name)) { + const CvxMiningFactory = await ethers.getContractFactory('CvxMining') + cvxMiningLib = await CvxMiningFactory.connect(burner).deploy() + await cvxMiningLib.deployed() + deployments.cvxMiningLib = cvxMiningLib.address + + fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + } + console.log(`Deployed to ${hre.network.name} (${chainId}): TradingLib: ${tradingLib.address} BasketLib: ${basketLib.address} + CvxMiningLib: ${cvxMiningLib ? cvxMiningLib.address : 'N/A'} Deployment file: ${deploymentFilename}`) } diff --git a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts b/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts index f33da05e81..e67a33f602 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, validateImplementations } from '../../deployment/utils' +import { priceTimeout, oracleTimeout, 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: '86400', // 24h + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 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 93a8a69392..cab49a5515 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 } from '../../deployment/utils' +import { priceTimeout, oracleTimeout } 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: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 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: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 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 5a58c3bea8..cd8fdaa334 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, revenueHiding } from '../utils' +import { combinedError, priceTimeout, oracleTimeout, 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: daiOracleTimeout, + oracleTimeout: oracleTimeout(chainId, daiOracleTimeout).toString(), 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: usdcOracleTimeout, // 24 hr + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -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: usdtOracleTimeout, // 24 hr + oracleTimeout: oracleTimeout(chainId, usdtOracleTimeout).toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdtOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -132,7 +132,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% tokenAddress: networkConfig[chainId].tokens.USDP, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 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: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.013').toString(), // 1.3% delayUntilDefault: bn('86400').toString(), // 24h @@ -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: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.015').toString(), // 1.5% delayUntilDefault: bn('86400').toString(), // 24h @@ -203,7 +203,7 @@ async function main() { oracleError: usdcOracleError.toString(), tokenAddress: networkConfig[chainId].tokens.USDbC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: usdcOracleTimeout, // 24 hr + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1.3% delayUntilDefault: bn('86400').toString(), // 24h @@ -249,7 +249,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% staticAToken: adaiStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -293,7 +293,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% staticAToken: ausdcStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -337,7 +337,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% staticAToken: ausdtStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -380,7 +380,7 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5% staticAToken: abusdStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.015').toString(), // 1.5% delayUntilDefault: bn('86400').toString(), // 24h @@ -424,7 +424,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% staticAToken: ausdpStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h @@ -445,242 +445,6 @@ 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 && @@ -694,8 +458,8 @@ async function main() { combinedOracleError: combinedBTCWBTCError.toString(), tokenAddress: networkConfig[chainId].tokens.WBTC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr - targetUnitOracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h @@ -713,7 +477,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', { @@ -722,7 +486,7 @@ async function main() { oracleError: ethOracleError.toString(), tokenAddress: networkConfig[chainId].tokens.WETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: ethOracleTimeout, + oracleTimeout: oracleTimeout(chainId, ethOracleTimeout).toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), }) collateral = await ethers.getContractAt('ICollateral', wETHCollateral) @@ -751,8 +515,8 @@ async function main() { oracleError: eurtError.toString(), // 2% tokenAddress: networkConfig[chainId].tokens.EURT, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr - targetUnitOracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetUnitOracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: fp('0.03').toString(), // 3% delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/deployment/phase2-assets/assets/deploy_crv.ts b/scripts/deployment/phase2-assets/assets/deploy_crv.ts index 80eb3f6c19..f0db202b9b 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 } from '../../../deployment/utils' +import { priceTimeout, oracleTimeout } 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: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 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 cb7eb2d5d2..1c5aaa57ce 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 } from '../../../deployment/utils' +import { priceTimeout, oracleTimeout } 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: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 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 f930201a21..e7fbc98512 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 } from '../../utils' +import { priceTimeout, revenueHiding, oracleTimeout } 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: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, bn('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 2d4eb8112d..2d56f9d3f9 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 } from '../../utils' +import { priceTimeout, revenueHiding, oracleTimeout } 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: usdcOracleTimeout, + oracleTimeout: oracleTimeout(chainId, 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 18984099fe..eab6850157 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, combinedError } from '../../utils' +import { priceTimeout, oracleTimeout, 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: '3600', // 1 hr, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - '86400' // refPerTokChainlinkTimeout + oracleTimeout(chainId, '86400').toString() // 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: '1200', // 20 min + oracleTimeout: oracleTimeout(chainId, '1200').toString(), // 20 min targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~2.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - '86400', // refPerTokChainlinkTimeout + oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout networkConfig[chainId].chainlinkFeeds.cbETHETHexr!, // exchangeRateChainlinkFeed - '86400' // exchangeRateChainlinkTimeout + oracleTimeout(chainId, '86400').toString() // 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 89b2464c55..d328a311dd 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, revenueHiding } from '../../utils' -import { ICollateral } from '../../../../typechain' +import { combinedError, priceTimeout, oracleTimeout, revenueHiding } from '../../utils' +import { ICollateral, ATokenMock, StaticATokenLM } 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: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -109,7 +109,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: cUsdcVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -147,7 +147,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: cUsdtVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -185,7 +185,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% cToken: cUsdpVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 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: '86400', // 24 hr - targetUnitOracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h @@ -265,7 +265,7 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5% cToken: cETHVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 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 b382962e58..dae7875b30 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts @@ -14,7 +14,7 @@ import { fileExists, } from '../../common' import { CurveStableRTokenMetapoolCollateral } from '../../../../typechain' -import { revenueHiding } from '../../utils' +import { revenueHiding, oracleTimeout } from '../../utils' import { CurvePoolType, DEFAULT_THRESHOLD, @@ -63,10 +63,13 @@ async function main() { /******** Deploy Convex Stable Metapool for eUSD/fraxBP **************************/ + const CvxMining = await ethers.getContractAt('CvxMining', deployments.cvxMiningLib) const CurveStableCollateralFactory = await hre.ethers.getContractFactory( 'CurveStableRTokenMetapoolCollateral' ) - const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { + libraries: { CvxMining: CvxMining.address }, + }) const wPool = await ConvexStakingWrapperFactory.deploy() await wPool.deployed() @@ -84,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: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -95,7 +98,10 @@ async function main() { curvePool: FRAX_BP, poolType: CurvePoolType.Plain, feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, 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 6727ff25d7..72d0f7debe 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts @@ -13,7 +13,7 @@ import { fileExists, } from '../../common' import { CurveStableMetapoolCollateral } from '../../../../typechain' -import { revenueHiding } from '../../utils' +import { revenueHiding, oracleTimeout } from '../../utils' import { CurvePoolType, DELAY_UNTIL_DEFAULT, @@ -69,10 +69,13 @@ async function main() { /******** Deploy Convex Stable Metapool for MIM/3Pool **************************/ + const CvxMining = await ethers.getContractAt('CvxMining', deployments.cvxMiningLib) const CurveStableCollateralFactory = await hre.ethers.getContractFactory( 'CurveStableMetapoolCollateral' ) - const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { + libraries: { CvxMining: CvxMining.address }, + }) const wPool = await ConvexStakingWrapperFactory.deploy() await wPool.deployed() @@ -102,7 +105,11 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, 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 abd65d88b6..97a8fc4f2b 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 } from '../../utils' +import { revenueHiding, oracleTimeout } from '../../utils' import { CurvePoolType, DAI_ORACLE_ERROR, @@ -65,8 +65,11 @@ async function main() { /******** Deploy Convex Stable Pool for 3pool **************************/ + const CvxMining = await ethers.getContractAt('CvxMining', deployments.cvxMiningLib) const CurveStableCollateralFactory = await hre.ethers.getContractFactory('CurveStableCollateral') - const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { + libraries: { CvxMining: CvxMining.address }, + }) const w3Pool = await ConvexStakingWrapperFactory.deploy() await w3Pool.deployed() @@ -85,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: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -96,7 +99,11 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, } diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts new file mode 100644 index 0000000000..ab71316676 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts @@ -0,0 +1,142 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + 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 21e78893af..3f4755ff72 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, revenueHiding } from '../../utils' +import { priceTimeout, oracleTimeout, 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: usdcOracleTimeout, // 24h hr, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1% + 0.3% delayUntilDefault: bn('86400').toString(), // 24h 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 a05ac5bbc7..873b8fbcc0 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, revenueHiding } from '../../utils' +import { priceTimeout, oracleTimeout, 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: usdcOracleTimeout, // 24h hr, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 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 c2d760a2f9..e886d6d806 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts @@ -13,7 +13,7 @@ import { fileExists, } from '../../common' import { CurveStableRTokenMetapoolCollateral } from '../../../../typechain' -import { revenueHiding } from '../../utils' +import { revenueHiding, oracleTimeout } 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: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -99,7 +99,10 @@ async function main() { curvePool: FRAX_BP, poolType: CurvePoolType.Plain, feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, 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 e62840d7e1..f97882d214 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 } from '../../utils' +import { revenueHiding, oracleTimeout } from '../../utils' import { CRV, CurvePoolType, @@ -105,7 +105,11 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, 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 6b9f415d01..b2d6462d6c 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 } from '../../utils' +import { revenueHiding, oracleTimeout } 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: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -100,7 +100,11 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, } diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts new file mode 100644 index 0000000000..f3b6e3e615 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts @@ -0,0 +1,142 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { CurveVolatileCollateral } from '../../../../typechain' +import { revenueHiding, oracleTimeout } from '../../utils' +import { + CRV, + CurvePoolType, + BTC_USD_ORACLE_ERROR, + BTC_ORACLE_TIMEOUT, + BTC_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + TRI_CRYPTO, + TRI_CRYPTO_TOKEN, + TRI_CRYPTO_GAUGE, + WBTC_BTC_ORACLE_ERROR, + WETH_ORACLE_TIMEOUT, + WBTC_BTC_FEED, + WBTC_ORACLE_TIMEOUT, + WETH_USD_FEED, + WETH_ORACLE_ERROR, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../../test/plugins/individual-collateral/curve/constants' + +// Deploy Curve Volatile Plugin for Tricrypto + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Curve Volatile Pool for 3pool **************************/ + + const CurveVolatileCollateralFactory = await hre.ethers.getContractFactory( + 'CurveVolatileCollateral' + ) + const CurveStakingWrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + const w3Pool = await CurveStakingWrapperFactory.deploy( + TRI_CRYPTO_TOKEN, + 'Wrapped Curve.fi USD-BTC-ETH', + 'wcrv3crypto', + CRV, + TRI_CRYPTO_GAUGE + ) + await w3Pool.deployed() + + console.log( + `Deployed wrapper for Curve Volatile TriCrypto on ${hre.network.name} (${chainId}): ${w3Pool.address} ` + ) + + const collateral = await CurveVolatileCollateralFactory.connect( + deployer + ).deploy( + { + erc20: w3Pool.address, + targetName: ethers.utils.formatBytes32String('TRICRYPTO'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: 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 aa6e840436..26ab8341ac 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 } from '../../utils' +import { priceTimeout, oracleTimeout } from '../../utils' import { SDaiCollateral } from '../../../../typechain' import { ContractFactory } from 'ethers' @@ -54,12 +54,12 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% erc20: networkConfig[chainId].tokens.sDAI, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h }, - bn(0), // does not require revenue hiding + fp('1e-6').toString(), // revenueHiding = 0.0001% POT ) await collateral.deployed() 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 6acbcccf8f..af7258ec4c 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, revenueHiding } from '../../utils' +import { priceTimeout, oracleTimeout, 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: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -74,7 +74,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: fUsdt.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -99,7 +99,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: fDai.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -124,7 +124,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% cToken: fFrax.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 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 30884eae2d..bc6a8b160a 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 } from '../../utils' +import { priceTimeout, oracleTimeout } 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: '3600', // 1 hr, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 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 - '86400' // targetPerRefChainlinkTimeout + oracleTimeout(chainId, '86400').toString() // 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 cb659b1889..535b4fd9f1 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, combinedError } from '../../utils' +import { priceTimeout, oracleTimeout, combinedError } from '../../utils' async function main() { // ==== Read Configuration ==== @@ -46,9 +46,11 @@ 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!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, underlyingERC20: networkConfig[chainId].tokens.USDT!, poolToken: networkConfig[chainId].tokens.aUSDT!, rewardToken: networkConfig[chainId].tokens.MORPHO!, @@ -57,6 +59,7 @@ async function main() { const maUSDC = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, underlyingERC20: networkConfig[chainId].tokens.USDC!, poolToken: networkConfig[chainId].tokens.aUSDC!, rewardToken: networkConfig[chainId].tokens.MORPHO!, @@ -65,6 +68,7 @@ async function main() { const maDAI = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, underlyingERC20: networkConfig[chainId].tokens.DAI!, poolToken: networkConfig[chainId].tokens.aDAI!, rewardToken: networkConfig[chainId].tokens.MORPHO!, @@ -73,6 +77,7 @@ async function main() { const maWBTC = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, underlyingERC20: networkConfig[chainId].tokens.WBTC!, poolToken: networkConfig[chainId].tokens.aWBTC!, rewardToken: networkConfig[chainId].tokens.MORPHO!, @@ -81,6 +86,7 @@ async function main() { const maWETH = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, underlyingERC20: networkConfig[chainId].tokens.WETH!, poolToken: networkConfig[chainId].tokens.aWETH!, rewardToken: networkConfig[chainId].tokens.MORPHO!, @@ -89,6 +95,7 @@ async function main() { const maStETH = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, underlyingERC20: networkConfig[chainId].tokens.stETH!, poolToken: networkConfig[chainId].tokens.astETH!, rewardToken: networkConfig[chainId].tokens.MORPHO!, @@ -120,7 +127,7 @@ async function main() { priceTimeout: priceTimeout.toString(), oracleError: stablesOracleError.toString(), maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24h + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: stablesOracleError.add(fp('0.01')), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -158,7 +165,6 @@ async function main() { const collateral = await FiatCollateralFactory.connect(deployer).deploy( { ...baseStableConfig, - oracleTimeout: '3600', // 1 hr chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI!, erc20: maDAI.address, }, @@ -179,16 +185,16 @@ async function main() { priceTimeout: priceTimeout, oracleError: combinedBTCWBTCError, maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% delayUntilDefault: bn('86400'), // 24h - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.WBTC!, // {target/ref} + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.BTC!, // {UoA/target} erc20: maWBTC.address, }, revenueHiding, - networkConfig[chainId].chainlinkFeeds.BTC!, // {UoA/target} - '3600' // 1 hr + networkConfig[chainId].chainlinkFeeds.WBTC!, // {target/ref} + oracleTimeout(chainId, '86400').toString() // 1 hr ) assetCollDeployments.collateral.maWBTC = collateral.address deployedCollateral.push(collateral.address.toString()) @@ -202,7 +208,7 @@ async function main() { priceTimeout: priceTimeout, oracleError: fp('0.005'), maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0'), // 0% -- no soft default for self-referential collateral delayUntilDefault: bn('86400'), // 24h @@ -231,16 +237,16 @@ async function main() { priceTimeout: priceTimeout, oracleError: combinedOracleErrors, maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.01').add(combinedOracleErrors), // ~1.5% delayUntilDefault: bn('86400'), // 24h - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.stETHETH!, // {target/ref} + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, // {UoA/target} erc20: maStETH.address, }, revenueHiding, - networkConfig[chainId].chainlinkFeeds.ETH!, // {UoA/target} - '3600' // 1 hr + networkConfig[chainId].chainlinkFeeds.stETHETH!, // {target/ref} + oracleTimeout(chainId, '86400').toString() // 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 d90520b97a..7f8b308993 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, combinedError } from '../../utils' +import { priceTimeout, oracleTimeout, 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: '3600', // 1 hr, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% rethOracleAddress, // refPerTokChainlinkFeed - '86400' // refPerTokChainlinkTimeout + oracleTimeout(chainId, '86400').toString() // refPerTokChainlinkTimeout ) await collateral.deployed() await (await collateral.refresh()).wait() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_sfrax.ts b/scripts/deployment/phase2-assets/collaterals/deploy_sfrax.ts index 600505d84e..1510377f20 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_sfrax.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_sfrax.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout } from '../../utils' +import { priceTimeout, oracleTimeout } from '../../utils' import { SFraxCollateral } from '../../../../typechain' import { ContractFactory } from 'ethers' @@ -53,7 +53,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% erc20: networkConfig[chainId].tokens.sFRAX, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% = 1% oracleError + 1% buffer delayUntilDefault: bn('86400').toString(), // 24h 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 dfd837767f..5db4436ea9 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts @@ -12,12 +12,17 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { revenueHiding, priceTimeout } from '../../utils' +import { revenueHiding, priceTimeout, oracleTimeout } from '../../utils' import { StargatePoolFiatCollateral, StargatePoolFiatCollateral__factory, } from '../../../../typechain' import { ContractFactory } from 'ethers' + +import { + STAKING_CONTRACT, + SUSDC, +} from '../../../../test/plugins/individual-collateral/stargate/constants' import { useEnv } from '#/utils/env' async function main() { @@ -46,10 +51,8 @@ async function main() { /******** Deploy Stargate USDC Wrapper **************************/ - const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory( - 'StargateRewardableWrapper' - ) - const chainIdKey = useEnv('FORK_NETWORK', 'mainnet') == 'mainnet' ? '1' : '8453' + const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory('StargateRewardableWrapper') + let chainIdKey = useEnv('FORK_NETWORK', 'mainnet') == 'mainnet' ? '1' : '8453' let USDC_NAME = 'USDC' let name = 'Wrapped Stargate USDC' let symbol = 'wsgUSDC' @@ -90,7 +93,7 @@ async function main() { oracleError: oracleError.toString(), erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24h hr, + oracleTimeout: oracleTimeout(chainIdKey, '86400').toString(), // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(oracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -101,9 +104,7 @@ 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 4ac4e4c6c8..8a43556a52 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 } from '../../utils' +import { revenueHiding, priceTimeout, oracleTimeout } 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: '86400', // 24h hr, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 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/phase2-assets/collaterals/deploy_yearn_v2_curve_usdc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_yearn_v2_curve_usdc.ts deleted file mode 100644 index e8f9f1f154..0000000000 --- a/scripts/deployment/phase2-assets/collaterals/deploy_yearn_v2_curve_usdc.ts +++ /dev/null @@ -1,104 +0,0 @@ -import fs from 'fs' -import hre from 'hardhat' -import { getChainId } from '../../../../common/blockchain-utils' -import { networkConfig } from '../../../../common/configuration' -import { bn, fp } from '../../../../common/numbers' -import { expect } from 'chai' -import { CollateralStatus } from '../../../../common/constants' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, - getDeploymentFilename, - fileExists, -} from '../../common' -import { priceTimeout } from '../../utils' -import { YearnV2CurveFiatCollateral } from '../../../../typechain' -import { ContractFactory } from 'ethers' -import { - PRICE_PER_SHARE_HELPER, - YVUSDC_LP_TOKEN, -} from '../../../../test/plugins/individual-collateral/yearnv2/constants' - -async function main() { - // ==== Read Configuration ==== - const [deployer] = await hre.ethers.getSigners() - - const chainId = await getChainId(hre) - - console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) - with burner account: ${deployer.address}`) - - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - // Get phase1 deployment - const phase1File = getDeploymentFilename(chainId) - if (!fileExists(phase1File)) { - throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) - } - // Check previous step completed - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) - - const deployedCollateral: string[] = [] - - /******** Deploy Yearn V2 Curve Fiat Collateral - yvCurveUSDCcrvUSD **************************/ - - const YearnV2CurveCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( - 'YearnV2CurveFiatCollateral' - ) - - const collateral = await YearnV2CurveCollateralFactory.connect( - deployer - ).deploy( - { - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC, // not used but can't be empty - oracleError: fp('0.0025').toString(), // not used but can't be empty - erc20: networkConfig[chainId].tokens.yvCurveUSDCcrvUSD, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24hr -- max of all oracleTimeouts - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.015').toString(), // 1.5% = max oracleError + 1% - delayUntilDefault: bn('86400').toString(), // 24h - }, - fp('1e-6').toString(), // revenueHiding = 0.0001%, low since underlying curve pool should be up-only - { - nTokens: '2', - curvePool: YVUSDC_LP_TOKEN, - poolType: '0', - feeds: [ - [networkConfig[chainId].chainlinkFeeds.USDC], - [networkConfig[chainId].chainlinkFeeds.crvUSD], - ], - oracleTimeouts: [['86400'], ['86400']], - oracleErrors: [[fp('0.0025').toString()], [fp('0.005').toString()]], - lpToken: YVUSDC_LP_TOKEN, - }, - PRICE_PER_SHARE_HELPER - ) - await collateral.deployed() - - console.log( - `Deployed Yearn Curve yvUSDCcrvUSD to ${hre.network.name} (${chainId}): ${collateral.address}` - ) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.yvCurveUSDCcrvUSD = collateral.address - assetCollDeployments.erc20s.yvCurveUSDCcrvUSD = networkConfig[chainId].tokens.yvCurveUSDCcrvUSD - 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_yearn_v2_curve_usdp.ts b/scripts/deployment/phase2-assets/collaterals/deploy_yearn_v2_curve_usdp.ts deleted file mode 100644 index cbb2c89cc0..0000000000 --- a/scripts/deployment/phase2-assets/collaterals/deploy_yearn_v2_curve_usdp.ts +++ /dev/null @@ -1,104 +0,0 @@ -import fs from 'fs' -import hre from 'hardhat' -import { getChainId } from '../../../../common/blockchain-utils' -import { networkConfig } from '../../../../common/configuration' -import { bn, fp } from '../../../../common/numbers' -import { expect } from 'chai' -import { CollateralStatus } from '../../../../common/constants' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, - getDeploymentFilename, - fileExists, -} from '../../common' -import { priceTimeout } from '../../utils' -import { YearnV2CurveFiatCollateral } from '../../../../typechain' -import { ContractFactory } from 'ethers' -import { - PRICE_PER_SHARE_HELPER, - YVUSDP_LP_TOKEN, -} from '../../../../test/plugins/individual-collateral/yearnv2/constants' - -async function main() { - // ==== Read Configuration ==== - const [deployer] = await hre.ethers.getSigners() - - const chainId = await getChainId(hre) - - console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) - with burner account: ${deployer.address}`) - - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - // Get phase1 deployment - const phase1File = getDeploymentFilename(chainId) - if (!fileExists(phase1File)) { - throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) - } - // Check previous step completed - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) - - const deployedCollateral: string[] = [] - - /******** Deploy Yearn V2 Curve Fiat Collateral - yvCurveUSDPcrvUSD **************************/ - - const YearnV2CurveCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( - 'YearnV2CurveFiatCollateral' - ) - - const collateral = await YearnV2CurveCollateralFactory.connect( - deployer - ).deploy( - { - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDP, // not used but can't be empty - oracleError: fp('0.0025').toString(), // not used but can't be empty - erc20: networkConfig[chainId].tokens.yvCurveUSDPcrvUSD, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24hr -- max of all oracleTimeouts - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.02').toString(), // 2% = max oracleError + 1% - delayUntilDefault: bn('86400').toString(), // 24h - }, - fp('1e-6').toString(), // revenueHiding = 0.0001%, low since underlying curve pool should be up-only - { - nTokens: '2', - curvePool: YVUSDP_LP_TOKEN, - poolType: '0', - feeds: [ - [networkConfig[chainId].chainlinkFeeds.USDP], - [networkConfig[chainId].chainlinkFeeds.crvUSD], - ], - oracleTimeouts: [['3600'], ['86400']], - oracleErrors: [[fp('0.01').toString()], [fp('0.005').toString()]], - lpToken: YVUSDP_LP_TOKEN, - }, - PRICE_PER_SHARE_HELPER - ) - await collateral.deployed() - - console.log( - `Deployed Yearn Curve yvUSDPcrvUSD to ${hre.network.name} (${chainId}): ${collateral.address}` - ) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.yvCurveUSDPcrvUSD = collateral.address - assetCollDeployments.erc20s.yvCurveUSDPcrvUSD = networkConfig[chainId].tokens.yvCurveUSDPcrvUSD - 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/utils.ts b/scripts/deployment/utils.ts index a8c2083e09..84dad2be5e 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 } from 'ethers' +import { BigNumber, BigNumberish } from 'ethers' import { bn, fp } from '../../common/numbers' import { IComponents, baseL2Chains } from '../../common/configuration' import { isValidContract } from '../../common/blockchain-utils' @@ -13,6 +13,13 @@ 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/exhaustive-tests/run-1.sh b/scripts/exhaustive-tests/run-1.sh index bf214a6ba0..fbad597e14 100644 --- a/scripts/exhaustive-tests/run-1.sh +++ b/scripts/exhaustive-tests/run-1.sh @@ -1,3 +1,3 @@ -echo "Running Broker, RToken, & Furnace exhaustive tests for commit hash: " +echo "Running RToken & Furnace exhaustive tests for commit hash: " git rev-parse HEAD; -NODE_OPTIONS=--max-old-space-size=30000 EXTREME=1 SLOW=1 PROTO_IMPL=1 npx hardhat test test/RTokenExtremes.test.ts test/Broker.test.ts test/Furnace.test.ts; +NODE_OPTIONS=--max-old-space-size=30000 EXTREME=1 SLOW=1 PROTO_IMPL=1 npx hardhat test test/RTokenExtremes.test.ts test/Furnace.test.ts; diff --git a/scripts/verification/6_verify_collateral.ts b/scripts/verification/6_verify_collateral.ts index fbcfe84d23..f857f28877 100644 --- a/scripts/verification/6_verify_collateral.ts +++ b/scripts/verification/6_verify_collateral.ts @@ -8,7 +8,13 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../deployment/common' -import { combinedError, priceTimeout, revenueHiding, verifyContract } from '../deployment/utils' +import { + combinedError, + priceTimeout, + oracleTimeout, + revenueHiding, + verifyContract, +} from '../deployment/utils' import { ATokenMock, ATokenFiatCollateral, ICToken, CTokenFiatCollateral } from '../../typechain' let deployments: IAssetCollDeployments @@ -41,7 +47,7 @@ async function main() { oracleError: daiOracleError.toString(), erc20: networkConfig[chainId].tokens.DAI, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: daiOracleTimeout, + oracleTimeout: oracleTimeout(chainId, daiOracleTimeout).toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(daiOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -65,7 +71,7 @@ async function main() { oracleError: usdcOracleError.toString(), erc20: networkConfig[chainId].tokens.USDbC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: usdcOracleTimeout, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -93,7 +99,7 @@ async function main() { 'Static ' + (await aToken.name()), 's' + (await aToken.symbol()), ], - 'contracts/plugins/assets/aave/vendor/StaticATokenLM.sol:StaticATokenLM' + 'contracts/plugins/assets/aave/StaticATokenLM.sol:StaticATokenLM' ) /******** Verify ATokenFiatCollateral - aDAI **************************/ await verifyContract( @@ -106,7 +112,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% erc20: await aTokenCollateral.erc20(), maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -145,7 +151,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% erc20: deployments.erc20s.cDAI, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -170,13 +176,13 @@ async function main() { oracleError: combinedBTCWBTCError.toString(), erc20: deployments.erc20s.cWBTC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 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, - '3600', + oracleTimeout(chainId, '3600').toString(), revenueHiding.toString(), ], 'contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol:CTokenNonFiatCollateral' @@ -192,7 +198,7 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5% erc20: deployments.erc20s.cETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: '0', delayUntilDefault: '0', @@ -213,13 +219,13 @@ async function main() { oracleError: combinedBTCWBTCError.toString(), erc20: networkConfig[chainId].tokens.WBTC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24h + oracleTimeout: oracleTimeout(chainId, '86400').toString(), targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h }, networkConfig[chainId].chainlinkFeeds.BTC, - '3600', + oracleTimeout(chainId, '3600').toString(), ], 'contracts/plugins/assets/NonFiatCollateral.sol:NonFiatCollateral' ) @@ -238,7 +244,7 @@ async function main() { oracleError: ethOracleError.toString(), // 0.5% erc20: networkConfig[chainId].tokens.WETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: ethOracleTimeout, + oracleTimeout: oracleTimeout(chainId, ethOracleTimeout).toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: '0', delayUntilDefault: '0', @@ -258,13 +264,13 @@ async function main() { oracleError: fp('0.02').toString(), // 2% erc20: networkConfig[chainId].tokens.EURT, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 24hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: fp('0.03').toString(), // 3% delayUntilDefault: bn('86400').toString(), // 24h }, networkConfig[chainId].chainlinkFeeds.EUR, - '86400', + oracleTimeout(chainId, '86400').toString(), ], '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 3a373573dc..edb092d1af 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, verifyContract, revenueHiding } from '../../deployment/utils' +import { priceTimeout, oracleTimeout, 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: '86400', // 24 hr + oracleTimeout: oracleTimeout(chainId, bn('86400')).toString(), // 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 3ffce9a0a5..1486c37cab 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, verifyContract, revenueHiding } from '../../deployment/utils' +import { priceTimeout, oracleTimeout, 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: usdcOracleTimeout, // 24 hr + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 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 9b52d6323b..4e58ad88d5 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, verifyContract, combinedError } from '../../deployment/utils' +import { priceTimeout, oracleTimeout, 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: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - '86400', // refPerTokChainlinkTimeout + oracleTimeout(chainId, '86400').toString(), // 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: '1200', // 20 min + oracleTimeout: oracleTimeout(chainId, '1200').toString(), // 20 min targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~2.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - '86400', // refPerTokChainlinkTimeout + oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout networkConfig[chainId].chainlinkFeeds.cbETHETHexr!, // exchangeRateChainlinkFeed - '86400', // exchangeRateChainlinkTimeout + oracleTimeout(chainId, '86400').toString(), // 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 127ef39143..3c22ae2557 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 } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' import { CurvePoolType, DAI_ORACLE_ERROR, @@ -61,7 +61,17 @@ async function main() { chainId, await w3PoolCollateral.erc20(), [], - 'contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper' + 'contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper', + { CvxMining: coreDeployments.cvxMiningLib } + ) + + /******** Verify CvxMining Lib **************************/ + + await verifyContract( + chainId, + coreDeployments.cvxMiningLib, + [], + 'contracts/plugins/assets/curve/cvx/vendor/CvxMining.sol:CvxMining' ) /******** Verify 3Pool plugin **************************/ @@ -75,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: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -86,7 +96,11 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, diff --git a/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts b/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts index fedb2d418a..440f8854b2 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 } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' import { CurvePoolType, DAI_ORACLE_ERROR, @@ -75,7 +75,11 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, 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 400c23d10e..771f6bc767 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 } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } 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: USDC_ORACLE_TIMEOUT, + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -70,7 +70,10 @@ async function main() { curvePool: FRAX_BP, poolType: CurvePoolType.Plain, feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, 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 new file mode 100644 index 0000000000..8c48da0e56 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_convex_volatile.ts @@ -0,0 +1,98 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { bn } from '../../../common/numbers' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { + CurvePoolType, + BTC_USD_ORACLE_ERROR, + BTC_ORACLE_TIMEOUT, + BTC_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + TRI_CRYPTO, + TRI_CRYPTO_TOKEN, + WBTC_BTC_ORACLE_ERROR, + WETH_ORACLE_TIMEOUT, + WBTC_BTC_FEED, + WBTC_ORACLE_TIMEOUT, + WETH_USD_FEED, + WETH_ORACLE_ERROR, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../test/plugins/individual-collateral/curve/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const wTriCrypto = await ethers.getContractAt( + 'CurveVolatileCollateral', + deployments.collateral.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 3f4b66190a..ce1120f618 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 } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } 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: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -83,7 +83,11 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, diff --git a/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts b/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts index e1b433bbd5..60be29f1e0 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 } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' import { CurvePoolType, DAI_ORACLE_ERROR, @@ -75,7 +75,11 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, 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 43d2172f10..a48df02d1f 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 } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } 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: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -70,7 +70,10 @@ async function main() { curvePool: FRAX_BP, poolType: CurvePoolType.Plain, feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleTimeouts: [ + [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, 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 new file mode 100644 index 0000000000..2f5c53b2c1 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_curve_volatile.ts @@ -0,0 +1,98 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { bn } from '../../../common/numbers' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { + CurvePoolType, + BTC_USD_ORACLE_ERROR, + BTC_ORACLE_TIMEOUT, + BTC_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + TRI_CRYPTO, + TRI_CRYPTO_TOKEN, + WBTC_BTC_ORACLE_ERROR, + WETH_ORACLE_TIMEOUT, + WBTC_BTC_FEED, + WBTC_ORACLE_TIMEOUT, + WETH_USD_FEED, + WETH_ORACLE_ERROR, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../test/plugins/individual-collateral/curve/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const wTriCrypto = await ethers.getContractAt( + 'CurveVolatileCollateral', + deployments.collateral.crvTriCrypto as string + ) + + /******** Verify TriCrypto plugin **************************/ + await verifyContract( + chainId, + deployments.collateral.crvTriCrypto, + [ + { + erc20: await wTriCrypto.erc20(), + targetName: ethers.utils.formatBytes32String('TRICRYPTO'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 3, + curvePool: TRI_CRYPTO, + poolType: CurvePoolType.Plain, + feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, WBTC_ORACLE_TIMEOUT), oracleTimeout(chainId, BTC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, WETH_ORACLE_TIMEOUT)], + ], + oracleErrors: [ + [USDT_ORACLE_ERROR], + [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], + [WETH_ORACLE_ERROR], + ], + lpToken: TRI_CRYPTO_TOKEN, + }, + ], + 'contracts/plugins/assets/convex/CurveVolatileCollateral.sol:CurveVolatileCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_cusdbcv3.ts b/scripts/verification/collateral-plugins/verify_cusdbcv3.ts index d0eb672ef2..c3a6cb314e 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, verifyContract, revenueHiding } from '../../deployment/utils' +import { priceTimeout, oracleTimeout, 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: usdcOracleTimeout, // 24h hr, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1% + 0.3% delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/verification/collateral-plugins/verify_cusdcv3.ts b/scripts/verification/collateral-plugins/verify_cusdcv3.ts index 09a6eceb34..62c1389289 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, verifyContract, revenueHiding } from '../../deployment/utils' +import { priceTimeout, oracleTimeout, 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: usdcOracleTimeout, // 24h hr, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 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 4f9e6d832b..ba7658f5af 100644 --- a/scripts/verification/collateral-plugins/verify_morpho.ts +++ b/scripts/verification/collateral-plugins/verify_morpho.ts @@ -7,7 +7,13 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../../deployment/common' -import { combinedError, priceTimeout, verifyContract, revenueHiding } from '../../deployment/utils' +import { + combinedError, + priceTimeout, + oracleTimeout, + verifyContract, + revenueHiding, +} from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -58,7 +64,7 @@ async function main() { priceTimeout: priceTimeout.toString(), oracleError: fp('0.0025').toString(), // 0.25% maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: '86400', // 1 hr + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0025').add(fp('0.01')).toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -86,7 +92,7 @@ async function main() { priceTimeout: priceTimeout.toString(), oracleError: combinedBTCWBTCError.toString(), // 0.25% maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% delayUntilDefault: bn('86400'), // 24h @@ -95,7 +101,7 @@ async function main() { }, revenueHiding, networkConfig[chainId].chainlinkFeeds.WBTC!, - '86400', // 1 hr + oracleTimeout(chainId, '86400').toString(), // 1 hr ], 'contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol:MorphoNonFiatCollateral' ) @@ -115,7 +121,7 @@ async function main() { priceTimeout: priceTimeout, oracleError: fp('0.005'), maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: '3600', // 1 hr + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0'), // 0% -- no soft default for self-referential collateral delayUntilDefault: bn('86400'), // 24h diff --git a/scripts/verification/collateral-plugins/verify_reth.ts b/scripts/verification/collateral-plugins/verify_reth.ts index 077cc76e0d..324a081859 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, verifyContract, combinedError } from '../../deployment/utils' +import { priceTimeout, oracleTimeout, 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: '3600', // 1 hr, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.rETH, // refPerTokChainlinkFeed - '86400', // refPerTokChainlinkTimeout + oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout ], 'contracts/plugins/assets/rocket-eth/RethCollateral.sol:RethCollateral' ) diff --git a/scripts/verification/collateral-plugins/verify_sdai.ts b/scripts/verification/collateral-plugins/verify_sdai.ts index 393c6264b3..e5d9290c39 100644 --- a/scripts/verification/collateral-plugins/verify_sdai.ts +++ b/scripts/verification/collateral-plugins/verify_sdai.ts @@ -42,7 +42,7 @@ async function main() { defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h }, - bn(0), + fp('1e-6').toString(), // revenueHiding = 0.0001% POT, ], 'contracts/plugins/assets/dsr/SDaiCollateral.sol:SDaiCollateral' diff --git a/scripts/verification/collateral-plugins/verify_wsteth.ts b/scripts/verification/collateral-plugins/verify_wsteth.ts index b84c9aad57..c0b73b2fb0 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, verifyContract } from '../../deployment/utils' +import { priceTimeout, oracleTimeout, 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: '3600', // 1 hr, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 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 - '86400', // targetPerRefChainlinkTimeout + oracleTimeout(chainId, '86400').toString(), // targetPerRefChainlinkTimeout ], 'contracts/plugins/assets/lido/LidoStakedEthCollateral.sol:LidoStakedEthCollateral' ) diff --git a/scripts/verification/collateral-plugins/verify_yearn_v2_curve_usdc.ts b/scripts/verification/collateral-plugins/verify_yearn_v2_curve_usdc.ts deleted file mode 100644 index 7505cfdb85..0000000000 --- a/scripts/verification/collateral-plugins/verify_yearn_v2_curve_usdc.ts +++ /dev/null @@ -1,73 +0,0 @@ -import hre from 'hardhat' -import { getChainId } from '../../../common/blockchain-utils' -import { developmentChains, networkConfig } from '../../../common/configuration' -import { fp, bn } from '../../../common/numbers' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, -} from '../../deployment/common' -import { priceTimeout, oracleTimeout, verifyContract } from '../../deployment/utils' -import { - PRICE_PER_SHARE_HELPER, - YVUSDC_LP_TOKEN, -} from '../../../test/plugins/individual-collateral/yearnv2/constants' - -let deployments: IAssetCollDeployments - -async function main() { - // ********** Read config ********** - const chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - if (developmentChains.includes(hre.network.name)) { - throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) - } - - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - deployments = getDeploymentFile(assetCollDeploymentFilename) - - /******** Verify yvCurveUSDCcrvUSD **************************/ - await verifyContract( - chainId, - deployments.collateral.yvCurveUSDCcrvUSD, - [ - { - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC, // not used but can't be empty - oracleError: fp('0.0025').toString(), // not used but can't be empty - erc20: networkConfig[chainId].tokens.yvCurveUSDCcrvUSD, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24hr -- max of all oracleTimeouts - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.015').toString(), // 1.5% = max oracleError + 1% - delayUntilDefault: bn('86400').toString(), // 24h - }, - fp('1e-6').toString(), // revenueHiding = 0.0001%, low since underlying curve pool should be up-only - { - nTokens: '2', - curvePool: YVUSDC_LP_TOKEN, - poolType: '0', - feeds: [ - networkConfig[chainId].chainlinkFeeds.USDC, - networkConfig[chainId].chainlinkFeeds.crvUSD, - ], - oracleTimeouts: [ - oracleTimeout(chainId, '86400').toString(), - oracleTimeout(chainId, '86400').toString(), - ], - oracleErrors: [fp('0.0025').toString(), fp('0.005').toString()], - lpToken: YVUSDC_LP_TOKEN, - }, - PRICE_PER_SHARE_HELPER, - ], - 'contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol:YearnV2CurveFiatCollateral' - ) -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index dd200b6248..a0a69c2281 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -62,8 +62,6 @@ async function main() { 'collateral-plugins/verify_sdai.ts', 'collateral-plugins/verify_morpho.ts', 'collateral-plugins/verify_aave_v3_usdc.ts', - 'collateral-plugins/verify_yearn_v2_curve_usdc.ts', - 'collateral-plugins/verify_yearn_v2_curve_usdp.ts', 'collateral-plugins/verify_sfrax.ts' ) } else if (chainId == '8453' || chainId == '84531') { diff --git a/tasks/deployment/deploy-facade-monitor.ts b/tasks/deployment/deploy-facade-monitor.ts deleted file mode 100644 index 290a77f647..0000000000 --- a/tasks/deployment/deploy-facade-monitor.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { getChainId } from '../../common/blockchain-utils' -import { task, types } from 'hardhat/config' -import { FacadeMonitor } from '../../typechain' -import { developmentChains, networkConfig, IMonitorParams } from '../../common/configuration' -import { ZERO_ADDRESS } from '../../common/constants' -import { ContractFactory } from 'ethers' - -let facadeMonitor: FacadeMonitor - -task( - 'deploy-facade-monitor', - 'Deploys the FacadeMonitor implementation and proxy (if its not an upgrade)' -) - .addParam('upgrade', 'Set to true if this is for a later upgrade', false, types.boolean) - .addOptionalParam('owner', 'The address that will own the FacadeMonitor', '', types.string) - .addOptionalParam('noOutput', 'Suppress output', false, types.boolean) - .setAction(async (params, hre) => { - const [wallet] = await hre.ethers.getSigners() - - const chainId = await getChainId(hre) - - // ********** Read config ********** - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - if (!params.upgrade) { - if (!params.owner) { - throw new Error( - `An --owner must be specified for the initial deployment to ${hre.network.name}` - ) - } - } - - if (!params.noOutput) { - console.log( - `Deploying FacadeMonitor to ${hre.network.name} (${chainId}) with burner account ${wallet.address}` - ) - } - - // Setup Monitor Params - const monitorParams: IMonitorParams = { - AAVE_V2_DATA_PROVIDER_ADDR: networkConfig[chainId].AAVE_DATA_PROVIDER ?? ZERO_ADDRESS, - } - - // Deploy FacadeMonitor - const FacadeMonitorFactory: ContractFactory = await hre.ethers.getContractFactory( - 'FacadeMonitor' - ) - const facadeMonitorImplAddr = (await hre.upgrades.deployImplementation(FacadeMonitorFactory, { - kind: 'uups', - constructorArgs: [monitorParams], - })) as string - - if (!params.noOutput) { - console.log( - `Deployed FacadeMonitor (Implementation) to ${hre.network.name} (${chainId}): ${facadeMonitorImplAddr}` - ) - } - - if (!params.upgrade) { - facadeMonitor = await hre.upgrades.deployProxy( - FacadeMonitorFactory, - [params.owner], - { - kind: 'uups', - initializer: 'init', - constructorArgs: [monitorParams], - } - ) - - if (!params.noOutput) { - console.log( - `Deployed FacadeMonitor (Proxy) to ${hre.network.name} (${chainId}): ${facadeMonitor.address}` - ) - } - } - // Verify if its not a development chain - if (!developmentChains.includes(hre.network.name)) { - // Uncomment to verify - if (!params.noOutput) { - console.log('sleeping 30s') - } - - // Sleep to ensure API is in sync with chain - await new Promise((r) => setTimeout(r, 30000)) // 30s - - if (!params.noOutput) { - console.log('verifying') - } - - /** ******************** Verify FacadeMonitor ****************************************/ - console.time('Verifying FacadeMonitor Implementation') - await hre.run('verify:verify', { - address: facadeMonitorImplAddr, - constructorArguments: [monitorParams], - contract: 'contracts/facade/FacadeMonitor.sol:FacadeMonitor', - }) - console.timeEnd('Verifying FacadeMonitor Implementation') - - if (!params.noOutput) { - console.log('verified') - } - } - - return { facadeMonitor: facadeMonitor ? facadeMonitor.address : 'N/A', facadeMonitorImplAddr } - }) diff --git a/tasks/index.ts b/tasks/index.ts index b1a9df3b56..4f167da7a5 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -16,7 +16,6 @@ import './deployment/mock/deploy-mock-aave' import './deployment/mock/deploy-mock-wbtc' import './deployment/mock/deploy-mock-easyauction' import './deployment/create-deployer-registry' -import './deployment/deploy-facade-monitor' import './deployment/empty-wallet' import './deployment/cancel-tx' import './deployment/sign-msg' diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 8ef43c0721..ff345cabd4 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -42,7 +42,7 @@ import { Implementation, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, PRICE_TIMEOUT, SLOW, } from './fixtures' @@ -54,7 +54,7 @@ import { getLatestBlockTimestamp, getLatestBlockNumber, } from './utils/time' -import { ITradeRequest, disableBatchTrade, disableDutchTrade } from './utils/trades' +import { ITradeRequest } from './utils/trades' import { useEnv } from '#/utils/env' import { parseUnits } from 'ethers/lib/utils' @@ -132,6 +132,30 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { prices = { sellLow: fp('1'), sellHigh: fp('1'), buyLow: fp('1'), buyHigh: fp('1') } }) + const disableBatchTrade = async () => { + if (IMPLEMENTATION == Implementation.P1) { + const slot = await getStorageAt(broker.address, 205) + await setStorageAt( + broker.address, + 205, + slot.replace(slot.slice(2, 14), '1'.padStart(12, '0')) + ) + } else { + const slot = await getStorageAt(broker.address, 56) + await setStorageAt(broker.address, 56, slot.replace(slot.slice(2, 42), '1'.padStart(40, '0'))) + } + expect(await broker.batchTradeDisabled()).to.equal(true) + } + + const disableDutchTrade = async (erc20: string) => { + const mappingSlot = IMPLEMENTATION == Implementation.P1 ? bn('208') : bn('57') + const p = mappingSlot.toHexString().slice(2).padStart(64, '0') + const key = erc20.slice(2).padStart(64, '0') + const slot = ethers.utils.keccak256('0x' + key + p) + await setStorageAt(broker.address, slot, '0x' + '1'.padStart(64, '0')) + expect(await broker.dutchTradeDisabled(erc20)).to.equal(true) + } + describe('Deployment', () => { it('Should setup Broker correctly', async () => { expect(await broker.gnosis()).to.equal(gnosis.address) @@ -388,7 +412,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) // Disable batch trade manually - await disableBatchTrade(broker) + await disableBatchTrade() expect(await broker.batchTradeDisabled()).to.equal(true) // Enable batch trade with owner @@ -401,7 +425,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) // Disable dutch trade manually - await disableDutchTrade(broker, token0.address) + await disableDutchTrade(token0.address) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(true) // Enable dutch trade with owner @@ -420,7 +444,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { describe('Trade Management', () => { it('Should not allow to open Batch trade if Disabled', async () => { // Disable Broker Batch Auctions - await disableBatchTrade(broker) + await disableBatchTrade() const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -449,13 +473,12 @@ 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) // Disable Broker Dutch Auctions for token0 - await disableDutchTrade(broker, token0.address) + await disableDutchTrade(token0.address) // Dutch Auction openTrade should fail now await expect( @@ -468,13 +491,12 @@ 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) // Disable Broker Dutch Auctions for token1 - await disableDutchTrade(broker, token1.address) + await disableDutchTrade(token1.address) // Dutch Auction openTrade should fail now await expect( @@ -550,6 +572,28 @@ 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', () => { @@ -1227,7 +1271,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: bn(500), - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1392,7 +1436,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { oracleError: bn('1'), // minimize erc20: sellTok.address, maxTradeVolume: MAX_UINT192, - oracleTimeout: MAX_UINT48.sub(300), + oracleTimeout: MAX_UINT48, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), // shouldn't matter delayUntilDefault: bn('604800'), // shouldn't matter @@ -1404,7 +1448,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { oracleError: bn('1'), // minimize erc20: buyTok.address, maxTradeVolume: MAX_UINT192, - oracleTimeout: MAX_UINT48.sub(300), + oracleTimeout: MAX_UINT48, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), // shouldn't matter delayUntilDefault: bn('604800'), // shouldn't matter @@ -1616,14 +1660,6 @@ 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') @@ -1651,9 +1687,6 @@ 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) ) @@ -1662,9 +1695,6 @@ 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) ) @@ -1673,9 +1703,6 @@ 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 f20428e2a4..207263b706 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -3,13 +3,11 @@ import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' -import { ethers, upgrades } from 'hardhat' +import { ethers } from 'hardhat' import { expectEvents } from '../common/events' -import { IConfig, IMonitorParams } from '#/common/configuration' +import { IConfig } from '#/common/configuration' import { bn, fp } from '../common/numbers' import { setOraclePrice } from './utils/oracles' -import { disableBatchTrade, disableDutchTrade } from './utils/trades' -import { whileImpersonating } from './utils/impersonation' import { Asset, BackingManagerP1, @@ -20,8 +18,6 @@ import { CTokenWrapperMock, ERC20Mock, FacadeAct, - FacadeMonitor, - FacadeMonitorV2, FacadeRead, FacadeTest, MockV3Aggregator, @@ -49,17 +45,9 @@ import { IMPLEMENTATION, defaultFixture, ORACLE_ERROR, - ORACLE_TIMEOUT, - PRICE_TIMEOUT, } from './fixtures' import { advanceBlocks, getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' -import { - CollateralStatus, - TradeKind, - MAX_UINT256, - ONE_PERIOD, - ZERO_ADDRESS, -} from '#/common/constants' +import { CollateralStatus, TradeKind, MAX_UINT256, ZERO_ADDRESS } from '#/common/constants' import { expectTrade } from './utils/trades' import { mintCollaterals } from './utils/tokens' @@ -67,7 +55,7 @@ const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.ski const itP1 = IMPLEMENTATION == Implementation.P1 ? it : it.skip -describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { +describe('FacadeRead + FacadeAct contracts', () => { let owner: SignerWithAddress let addr1: SignerWithAddress let addr2: SignerWithAddress @@ -95,7 +83,6 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { let facade: FacadeRead let facadeTest: FacadeTest let facadeAct: FacadeAct - let facadeMonitor: FacadeMonitor // Main let rToken: TestIRToken @@ -138,7 +125,6 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { facade, facadeAct, facadeTest, - facadeMonitor, rToken, main, basketHandler, @@ -284,7 +270,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { it('Should handle UNPRICED when returning issuable quantities', async () => { // Set unpriced assets, should return UoA = 0 - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await setOraclePrice(tokenAsset.address, MAX_UINT256.div(2).sub(1)) const [toks, quantities, uoas] = await facade.callStatic.issue(rToken.address, issueAmount) expect(toks.length).to.equal(4) expect(toks[0]).to.equal(token.address) @@ -297,9 +283,9 @@ describe('FacadeRead + FacadeAct + FacadeMonitor 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) - // Assets are unpriced + // Three assets are unpriced expect(uoas[0]).to.equal(0) - expect(uoas[1]).to.equal(0) + expect(uoas[1]).to.equal(issueAmount.div(4)) expect(uoas[2]).to.equal(0) expect(uoas[3]).to.equal(0) }) @@ -495,10 +481,6 @@ describe('FacadeRead + FacadeAct + FacadeMonitor 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 @@ -523,10 +505,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect(backing).to.equal(fp('1')) expect(overCollateralization).to.equal(fp('0.5')) - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) - await setOraclePrice(tokenAsset.address, bn('1e8')) - await setOraclePrice(usdcAsset.address, bn('1e8')) - await assetRegistry.refresh() + await setOraclePrice(rsrAsset.address, MAX_UINT256.div(2).sub(1)) ;[backing, overCollateralization] = await facade.callStatic.backingOverview(rToken.address) // Check values - Fully collateralized and no over-collateralization @@ -572,98 +551,98 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { }) it('Should return revenue + chain into FacadeAct.runRevenueAuctions', async () => { - // 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) + const traders = [rTokenTrader, rsrTrader] + const initialPrice = await usdcAsset.price() + // Set lotLow to 0 == revenueOverview() should not revert await setOraclePrice(usdcAsset.address, bn('0')) - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) - await setOraclePrice(tokenAsset.address, bn('1e8')) - await setOraclePrice(rsrAsset.address, bn('1e8')) - await assetRegistry.refresh() - - 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) + 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 } - 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') - - // 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)) - // Nothing should be settleable - expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) - - // Advance time till auction is over - await advanceBlocks(2 + auctionLength / 12) - - // Now should be settleable - const settleable = await facade.auctionsSettleable(rsrTrader.address) - expect(settleable.length).to.equal(1) - expect(settleable[0]).to.equal(token.address) - - // Another call to revenueOverview should settle and propose new auction - ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview( - rsrTrader.address - ) - - // Should repeat the same auctions - for (let i = 0; i < 8; i++) { - if (erc20s[i] == token.address) { - expect(canStart[i]).to.equal(true) - expect(surpluses[i]).to.equal(tokenSurplus) - } else { - expect(canStart[i]).to.equal(false) - expect(surpluses[i]).to.equal(0) + // 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) + } } - } - // Settle and start new auction - await facadeAct.runRevenueAuctions(rsrTrader.address, erc20sToStart, erc20sToStart, [ - TradeKind.DUTCH_AUCTION, - ]) + // Settle and start new auction + await facadeAct.runRevenueAuctions(trader.address, erc20sToStart, erc20sToStart, [ + TradeKind.DUTCH_AUCTION, + ]) - // Send additional revenues - await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) + // Send additional revenues + await token.connect(addr1).transfer(trader.address, tokenSurplus) - // Call revenueOverview, cannot open new auctions - ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview( - rsrTrader.address - ) - expect(canStart).to.eql(Array(8).fill(false)) + // Call revenueOverview, cannot open new auctions + ;[erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(trader.address) + expect(canStart).to.eql(Array(8).fill(false)) + } }) itP1('Should handle invalid versions when running revenueOverview', async () => { @@ -913,11 +892,7 @@ describe('FacadeRead + FacadeAct + FacadeMonitor 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) @@ -966,24 +941,16 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { }) it('Should return pending unstakings', async () => { - // Bump draftEra by seizing RSR when the withdrawal queue is empty - await rsr.connect(owner).mint(stRSRP1.address, 1) - await whileImpersonating(backingManager.address, async (signer) => { - await stRSRP1.connect(signer).seizeRSR(1) - }) - const draftEra = await stRSRP1.getDraftEra() - expect(draftEra).to.equal(2) - - // Stake const unstakeAmount = bn('10000e18') await rsr.connect(owner).mint(addr1.address, unstakeAmount.mul(10)) + + // Stake await rsr.connect(addr1).approve(stRSR.address, unstakeAmount.mul(10)) await stRSRP1.connect(addr1).stake(unstakeAmount.mul(10)) - await stRSRP1.connect(addr1).unstake(unstakeAmount) await stRSRP1.connect(addr1).unstake(unstakeAmount.add(1)) - const pendings = await facade.pendingUnstakings(rToken.address, draftEra, addr1.address) + const pendings = await facade.pendingUnstakings(rToken.address, addr1.address) expect(pendings.length).to.eql(2) expect(pendings[0][0]).to.eql(bn(0)) // index expect(pendings[0][2]).to.eql(unstakeAmount) // amount @@ -1067,339 +1034,6 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { } }) - describe('FacadeMonitor', () => { - const monitorParams: IMonitorParams = { - AAVE_V2_DATA_PROVIDER_ADDR: ZERO_ADDRESS, - } - - beforeEach(async () => { - // Mint Tokens - initialBal = bn('10000000000e18') - await token.connect(owner).mint(addr1.address, initialBal) - await usdc.connect(owner).mint(addr1.address, initialBal) - await aToken.connect(owner).mint(addr1.address, initialBal) - await cTokenVault.connect(owner).mint(addr1.address, initialBal) - - // Provide approvals - await token.connect(addr1).approve(rToken.address, initialBal) - await usdc.connect(addr1).approve(rToken.address, initialBal) - await aToken.connect(addr1).approve(rToken.address, initialBal) - await cTokenVault.connect(addr1).approve(rToken.address, initialBal) - }) - - it('should return batch auctions disabled correctly', async () => { - expect(await facadeMonitor.batchAuctionsDisabled(rToken.address)).to.equal(false) - - // Disable Broker Batch Auctions - await disableBatchTrade(broker) - - expect(await facadeMonitor.batchAuctionsDisabled(rToken.address)).to.equal(true) - }) - - it('should return dutch auctions disabled correctly', async () => { - expect(await facadeMonitor.dutchAuctionsDisabled(rToken.address)).to.equal(false) - - // Disable Broker Dutch Auctions for token0 - await disableDutchTrade(broker, token.address) - - expect(await facadeMonitor.dutchAuctionsDisabled(rToken.address)).to.equal(true) - }) - - it('should return issuance available', async () => { - expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) // no supply - - // Issue some RTokens (1%) - const issueAmount = bn('10000e18') - - // Issue rTokens (1%) - await rToken.connect(addr1).issue(issueAmount) - - // check throttles updated - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('0.99')) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Issue additional rTokens (another 1%) - await rToken.connect(addr1).issue(issueAmount) - - // Should be 2% down minus some recharging - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.be.closeTo( - fp('0.98'), - fp('0.001') - ) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Advance time significantly - await advanceTime(10000000) - - // Check new issuance available - fully recharged - expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Issuance #2 - Consume all throttle - const issueAmount2: BigNumber = config.issuanceThrottle.amtRate - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await rToken.connect(addr1).issue(issueAmount2) - - // Check new issuance available - all consumed - expect(await rToken.issuanceAvailable()).to.equal(bn(0)) - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(bn(0)) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - }) - - it('should return redemption available', async () => { - const issueAmount = bn('100000e18') - - // Decrease redemption allowed amount - const redeemThrottleParams = { amtRate: issueAmount.div(2), pctRate: fp('0.1') } // 50K - await rToken.connect(owner).setRedemptionThrottleParams(redeemThrottleParams) - - // Check with no supply - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - expect(await rToken.redemptionAvailable()).to.equal(bn(0)) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Issue some RTokens - await rToken.connect(addr1).issue(issueAmount) - - // check throttles - redemption still fully available - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('0.9')) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Redeem RTokens (50% of throttle) - await rToken.connect(addr1).redeem(issueAmount.div(4)) - - // check throttle - redemption allowed decreased to 50% - expect(await rToken.redemptionAvailable()).to.equal(issueAmount.div(4)) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('0.5')) - - // Advance time significantly - await advanceTime(10000000) - - // Check redemption available - fully recharged - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Redemption #2 - Consume all throttle - await rToken.connect(addr1).redeem(issueAmount.div(2)) - - // Check new redemption available - all consumed - expect(await rToken.redemptionAvailable()).to.equal(bn(0)) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(bn(0)) - }) - - it('Should handle issuance/redemption throttles correctly, using percent', async function () { - // Full issuance available. Nothing to redeem - expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) - expect(await rToken.redemptionAvailable()).to.equal(bn(0)) - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Issue full throttle - const issueAmount1: BigNumber = config.issuanceThrottle.amtRate - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await rToken.connect(addr1).issue(issueAmount1) - - // Check redemption throttles updated - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(bn(0)) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Advance time significantly - await advanceTime(1000000000) - - // Check new issuance available - fully recharged - expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) - expect(await rToken.redemptionAvailable()).to.equal(issueAmount1) - - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Issuance #2 - Full throttle again - will be processed - const issueAmount2: BigNumber = config.issuanceThrottle.amtRate - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await rToken.connect(addr1).issue(issueAmount2) - - // Check new issuance available - all consumed - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(bn(0)) - - // Check redemption throttle updated - fixed in max (does not exceed) - expect(await rToken.redemptionAvailable()).to.equal(config.redemptionThrottle.amtRate) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Set issuance throttle to percent only - const issuanceThrottleParams = { amtRate: fp('1'), pctRate: fp('0.1') } // 10% - await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) - - // Advance time significantly - await advanceTime(1000000000) - - // Check new issuance available - 10% of supply (2 M) = 200K - const supplyThrottle = bn('200000e18') - expect(await rToken.issuanceAvailable()).to.equal(supplyThrottle) - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - - // Check redemption throttle unchanged - expect(await rToken.redemptionAvailable()).to.equal(config.redemptionThrottle.amtRate) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Issuance #3 - Should be allowed, does not exceed supply restriction - const issueAmount3: BigNumber = bn('100000e18') - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await rToken.connect(addr1).issue(issueAmount3) - - // Check issuance throttle updated - Previous issuances recharged - expect(await rToken.issuanceAvailable()).to.equal(supplyThrottle.sub(issueAmount3)) - - // Hourly Limit: 210K (10% of total supply of 2.1 M) - // Available: 100 K / 201K (~ 0.47619) - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.be.closeTo( - fp('0.476'), - fp('0.001') - ) - - // Check redemption throttle unchanged - expect(await rToken.redemptionAvailable()).to.equal(config.redemptionThrottle.amtRate) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Check all issuances are confirmed - expect(await rToken.balanceOf(addr1.address)).to.equal( - issueAmount1.add(issueAmount2).add(issueAmount3) - ) - - // Advance time, issuance will recharge a bit - await advanceTime(100) - - // Now 50% of hourly limit available (~105.8K / 210 K) - expect(await rToken.issuanceAvailable()).to.be.closeTo(fp('105800'), fp('100')) - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.be.closeTo( - fp('0.5'), - fp('0.01') - ) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - const issueAmount4: BigNumber = fp('105800') - // Issuance #4 - almost all available - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await rToken.connect(addr1).issue(issueAmount4) - - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.be.closeTo( - fp('0.003'), - fp('0.001') - ) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Advance time significantly to fully recharge - await advanceTime(1000000000) - - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Check redemptions - // Set redemption throttle to percent only - const redemptionThrottleParams = { amtRate: fp('1'), pctRate: fp('0.1') } // 10% - await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) - - const totalSupply = await rToken.totalSupply() - expect(await rToken.redemptionAvailable()).to.equal(totalSupply.div(10)) // 10% - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Redeem half of the available throttle - await rToken.connect(addr1).redeem(totalSupply.div(10).div(2)) - - // About 52% now used of redemption throttle - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.be.closeTo( - fp('0.52'), - fp('0.01') - ) - - // Advance time significantly to fully recharge - await advanceTime(1000000000) - - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(fp('1')) - - // Redeem all remaining - await rToken.connect(addr1).redeem(await rToken.redemptionAvailable()) - - // Check all consumed - expect(await facadeMonitor.issuanceAvailable(rToken.address)).to.equal(fp('1')) - expect(await rToken.redemptionAvailable()).to.equal(bn(0)) - expect(await facadeMonitor.redemptionAvailable(rToken.address)).to.equal(bn(0)) - }) - - it('Should not allow empty owner on initialization', async () => { - const FacadeMonitorFactory: ContractFactory = await ethers.getContractFactory('FacadeMonitor') - - const newFacadeMonitor = await upgrades.deployProxy(FacadeMonitorFactory, [], { - constructorArgs: [monitorParams], - kind: 'uups', - }) - - await expect(newFacadeMonitor.init(ZERO_ADDRESS)).to.be.revertedWith('invalid owner address') - }) - - it('Should allow owner to transfer ownership', async () => { - expect(await facadeMonitor.owner()).to.equal(owner.address) - - // Attempt to transfer ownership with another account - await expect( - facadeMonitor.connect(addr1).transferOwnership(addr1.address) - ).to.be.revertedWith('Ownable: caller is not the owner') - - // Owner remains the same - expect(await facadeMonitor.owner()).to.equal(owner.address) - - // Transfer ownership with owner - await expect(facadeMonitor.connect(owner).transferOwnership(addr1.address)) - .to.emit(facadeMonitor, 'OwnershipTransferred') - .withArgs(owner.address, addr1.address) - - // Owner changed - expect(await facadeMonitor.owner()).to.equal(addr1.address) - }) - - it('Should only allow owner to upgrade', async () => { - const FacadeMonitorV2Factory: ContractFactory = await ethers.getContractFactory( - 'FacadeMonitorV2' - ) - const facadeMonitorV2 = await FacadeMonitorV2Factory.deploy(monitorParams) - - await expect( - facadeMonitor.connect(addr1).upgradeTo(facadeMonitorV2.address) - ).to.be.revertedWith('Ownable: caller is not the owner') - await expect(facadeMonitor.connect(owner).upgradeTo(facadeMonitorV2.address)).to.not.be - .reverted - }) - - it('Should upgrade correctly', async () => { - // Upgrading - const FacadeMonitorV2Factory: ContractFactory = await ethers.getContractFactory( - 'FacadeMonitorV2' - ) - const facadeMonitorV2: FacadeMonitorV2 = await upgrades.upgradeProxy( - facadeMonitor.address, - FacadeMonitorV2Factory, - { - constructorArgs: [monitorParams], - } - ) - - // Check address is maintained - expect(facadeMonitorV2.address).to.equal(facadeMonitor.address) - - // Check state is preserved - expect(await facadeMonitorV2.owner()).to.equal(owner.address) - - // Check new version is implemented - expect(await facadeMonitorV2.version()).to.equal('2.0.0') - - expect(await facadeMonitorV2.newValue()).to.equal(0) - await facadeMonitorV2.connect(owner).setNewValue(bn(1000)) - expect(await facadeMonitorV2.newValue()).to.equal(bn(1000)) - }) - }) - // P1 only describeP1('FacadeAct', () => { let issueAmount: BigNumber @@ -1505,7 +1139,10 @@ describe('FacadeRead + FacadeAct + FacadeMonitor contracts', () => { expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) // Advance time till auction ended - await advanceBlocks(1 + auctionLength / 12) + await advanceBlocks(2 + auctionLength / 12) + + // Settleable now + expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(1) // Settle and start new auction - Will retry await expectEvents( @@ -1530,6 +1167,18 @@ describe('FacadeRead + FacadeAct + FacadeMonitor 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 9176c71ac0..97210ce749 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -195,8 +195,8 @@ describe('FacadeWrite contract', () => { // Set governance params govParams = { - votingDelay: bn(7200), // 1 day - votingPeriod: bn(21600), // 3 days + votingDelay: bn(5), // 5 blocks + votingPeriod: bn(100), // 100 blocks proposalThresholdAsMicroPercent: bn(1e6), // 1% quorumPercent: bn(4), // 4% timelockDelay: bn(60 * 60 * 24), // 1 day diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index 84d16f32c7..15776210be 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 melt if frozen #fast', async () => { + it('Should not melt if frozen #fast', async () => { await main.connect(owner).freezeShort() - await furnace.connect(addr1).melt() + await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen') }) it('Should not melt any funds in the initial block #fast', async () => { @@ -450,57 +450,40 @@ 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, mintAmount) + await rToken.connect(addr1).transfer(furnace.address, bn('10e18')) await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) await furnace.melt() // Should have updated lastPayout + lastPayoutBal expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) - expect(await furnace.lastPayoutBal()).to.equal(mintAmount) + expect(await furnace.lastPayoutBal()).to.equal(bn('10e18')) - // Advance 100 periods -- should melt at old ratio - await setNextBlockTimestamp( - Number(await getLatestBlockTimestamp()) + 100 * Number(ONE_PERIOD) - ) + // Advance 99 periods -- should melt at old ratio + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 99 * Number(ONE_PERIOD)) - // Freeze and change ratio (melting as a pre-step) + // Freeze and change ratio await main.connect(owner).freezeForever() - await expect(furnace.connect(owner).setRatio(secondRatio)) + const maxRatio = bn('1e14') + await expect(furnace.connect(owner).setRatio(maxRatio)) .to.emit(furnace, 'RatioSet') - .withArgs(firstRatio, secondRatio) + .withArgs(config.rewardRatio, maxRatio) - // Should have melted + // Should have updated lastPayout + lastPayoutBal expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) - expect(await furnace.lastPayoutBal()).to.eq(fp('99.990000494983830300')) + expect(await furnace.lastPayoutBal()).to.equal(bn('10e18')) // no change - // Unfreeze and advance 100 periods + // Unfreeze and advance 1 period await main.connect(owner).unfreeze() - await setNextBlockTimestamp( - Number(await getLatestBlockTimestamp()) + 100 * Number(ONE_PERIOD) - ) + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) await expect(furnace.melt()).to.emit(rToken, 'Melted') - // Should have updated lastPayout + lastPayoutBal and melted at new ratio + // 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(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)) + expect(await furnace.lastPayoutBal()).to.equal(bn('9.999e18')) }) }) diff --git a/test/Governance.test.ts b/test/Governance.test.ts index 53b7f7d2f1..83dffb413a 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 = 7200 // 1 day (in blocks) - const VOTING_PERIOD = 21600 // 3 days (in blocks) + const VOTING_DELAY = 5 // 5 blocks + const VOTING_PERIOD = 100 // 100 blocks const PROPOSAL_THRESHOLD = 1e6 // 1% const QUORUM_PERCENTAGE = 4 // 4% @@ -306,39 +306,13 @@ 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') - let proposalDescription = 'Proposal #1 - Update Trading Delay to 360' - let proposalDescHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(proposalDescription)) + const proposalDescription = 'Proposal #1 - Update Trading Delay to 360' + const proposalDescHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(proposalDescription)) let encodedFunctionCall: string let stkAmt1: BigNumber let stkAmt2: BigNumber @@ -899,143 +873,5 @@ 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 9d00c3b0a6..788d4eba4b 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -70,8 +70,6 @@ import { Implementation, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, - ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from './fixtures' @@ -1182,7 +1180,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: newToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral0.oracleTimeout(), targetName: await ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral0.delayUntilDefault(), @@ -1204,7 +1202,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: newToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral0.oracleTimeout(), targetName: await ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral0.delayUntilDefault(), @@ -1230,7 +1228,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await gasGuzzlingColl.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral0.oracleTimeout(), targetName: await ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral0.delayUntilDefault(), @@ -1629,7 +1627,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: erc20s[5].address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral2.oracleTimeout(), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -1725,7 +1723,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: eurToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral0.oracleTimeout(), targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral1.delayUntilDefault(), @@ -1948,7 +1946,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral0.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral0.oracleTimeout(), targetName: ethers.utils.formatBytes32String('NEW_TARGET'), defaultThreshold: fp('0.01'), delayUntilDefault: await collateral0.delayUntilDefault(), @@ -2758,10 +2756,8 @@ 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 [0, FIX_MAX] - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) - await setOraclePrice(collateral0.address, bn('1e8')) - await assetRegistry.refresh() + // Set collateral1 price to invalid value that should produce [0, FIX_MAX] + await setOraclePrice(collateral1.address, MAX_UINT192) // Check BU price -- 1/4 of the basket has lost all its value const asset = await ethers.getContractAt('Asset', basketHandler.address) @@ -2803,7 +2799,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: await collateral2.maxTradeVolume(), - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral2.oracleTimeout(), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -2816,8 +2812,6 @@ 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() @@ -2838,7 +2832,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: await collateral2.maxTradeVolume(), - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral2.oracleTimeout(), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -2846,9 +2840,17 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { REVENUE_HIDING ) await assetRegistry.connect(owner).swapRegistered(newColl.address) - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await setOraclePrice(newColl.address, MAX_UINT192) // overflow + await expectUnpriced(newColl.address) await newColl.setTargetPerRef(1) - await expectUnpriced(basketHandler.address) + 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) }) it('Should handle overflow in price calculation and return [FIX_MAX, FIX_MAX] - case 1', async () => { @@ -2863,7 +2865,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral2.oracleTimeout(), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -2905,6 +2907,35 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(highPrice).to.equal(MAX_UINT192) }) + it('Should distinguish between price/lotPrice', async () => { + // Set basket with single collateral + await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')]) + await basketHandler.refreshBasket() + + await collateral0.refresh() + const [low, high] = await collateral0.price() + await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // oracle error + + // lotPrice() should begin at 100% + let [lowPrice, highPrice] = await basketHandler.price() + let [lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() + expect(lowPrice).to.equal(0) + expect(highPrice).to.equal(MAX_UINT192) + expect(lotLowPrice).to.be.eq(low) + expect(lotHighPrice).to.be.eq(high) + + // Advance time past 100% period -- lotPrice() should begin to fall + await advanceTime(await collateral0.oracleTimeout()) + ;[lowPrice, highPrice] = await basketHandler.price() + ;[lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() + expect(lowPrice).to.equal(0) + expect(highPrice).to.equal(MAX_UINT192) + expect(lotLowPrice).to.be.closeTo(low, low.div(bn('1e5'))) // small decay expected + expect(lotLowPrice).to.be.lt(low) + expect(lotHighPrice).to.be.closeTo(high, high.div(bn('1e5'))) // small decay expected + expect(lotHighPrice).to.be.lt(high) + }) + it('Should disable basket on asset deregistration + return quantities correctly', async () => { // Check values expect(await facadeTest.wholeBasketsHeldBy(rToken.address, addr1.address)).to.equal( @@ -3085,7 +3116,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral2.oracleTimeout(), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -3129,7 +3160,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral2.oracleTimeout(), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -3150,15 +3181,6 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await expectPrice(basketHandler.address, fp('0.75'), ORACLE_ERROR, true) }) - it('lotPrice (deprecated) is equal to price()', async () => { - const lotPrice = await basketHandler.lotPrice() - const price = await basketHandler.price() - expect(price.length).to.equal(2) - expect(lotPrice.length).to.equal(price.length) - expect(lotPrice[0]).to.equal(price[0]) - expect(lotPrice[1]).to.equal(price[1]) - }) - it('Should not put backup tokens with different targetName in the basket', async () => { // Swap out collateral for bad target name const CollFactory = await ethers.getContractFactory('FiatCollateral') @@ -3168,7 +3190,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: await collateral0.oracleTimeout(), 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 f5c8afa994..f11c415f6a 100644 --- a/test/RTokenExtremes.test.ts +++ b/test/RTokenExtremes.test.ts @@ -21,7 +21,7 @@ import { IMPLEMENTATION, ORACLE_ERROR, SLOW, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), delayUntilDefault: bn(86400), @@ -155,6 +155,7 @@ 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 83d0b04af6..e806d4b316 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -51,7 +51,6 @@ import { IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, - ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' @@ -644,7 +643,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: token1.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral1.delayUntilDefault(), @@ -657,7 +656,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: backupToken1.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await backupCollateral1.delayUntilDefault(), @@ -1016,6 +1015,9 @@ 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')]) @@ -1027,7 +1029,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - temporary IFFY->SOUND await advanceTime(Number(config.warmupPeriod) + 1) - // Set all assets to UNPRICED + // Set to sell price = 0 await advanceTime(Number(ORACLE_TIMEOUT.add(PRICE_TIMEOUT))) // Check state remains SOUND @@ -1186,8 +1188,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - it('Should recollateralize correctly when switching basket', async () => { - // Set oracle value out-of-range + it('Should recollateralize correctly when switching basket - Using lot price', async () => { + // Set price to unpriced (will use lotPrice to size trade) await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // Setup prime basket @@ -1216,7 +1218,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await toMinBuyAmt(sellAmt, fp('1'), fp('1')), 6 ).add(1) - // since within oracleTimeout, price() should still be at 100% of original price + // since within oracleTimeout lotPrice() should still be at 100% of original price await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) .to.emit(backingManager, 'TradeStarted') @@ -1350,10 +1352,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) expect(await token1.balanceOf(backingManager.address)).to.equal(0) - // Check price in USD of the current RToken -- should track backing out on auction + // Check price in USD of the current RToken -- no backing currently + const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR await expectRTokenPrice( rTokenAsset.address, - fp('1'), + rTokenPrice, ORACLE_ERROR, await backingManager.maxTradeSlippage(), config.minTradeVolume.mul((await assetRegistry.erc20s()).length) @@ -1473,10 +1476,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) expect(await token1.balanceOf(backingManager.address)).to.equal(0) - // Check price in USD of the current RToken -- should track balances out on trade + // Check price in USD of the current RToken -- no backing currently + const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR await expectRTokenPrice( rTokenAsset.address, - fp('1'), + rTokenPrice, ORACLE_ERROR, await backingManager.maxTradeSlippage(), config.minTradeVolume.mul((await assetRegistry.erc20s()).length) @@ -1606,10 +1610,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) expect(await token1.balanceOf(backingManager.address)).to.equal(0) - // Check price in USD of the current RToken -- backing is tracked while out on trade + // Check price in USD of the current RToken -- no backing currently + const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR await expectRTokenPrice( rTokenAsset.address, - fp('1'), + rTokenPrice, ORACLE_ERROR, await backingManager.maxTradeSlippage(), config.minTradeVolume.mul((await assetRegistry.erc20s()).length) @@ -2143,7 +2148,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: fp('25'), - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await backupCollateral1.delayUntilDefault(), @@ -2207,7 +2212,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ).div(2) const sellAmt = sellAmtBeforeSlippage .mul(BN_SCALE_FACTOR) - .div(BN_SCALE_FACTOR.sub(ORACLE_ERROR)) + .div(BN_SCALE_FACTOR.add(ORACLE_ERROR)) const minBuyAmt = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) @@ -2250,7 +2255,64 @@ 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 - const leftoverSellAmt = issueAmount.sub(sellAmt) + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], + emitted: true, + }, + ]) + const leftoverSellAmt = issueAmount.sub(sellAmt.mul(2)) + + // Check new auction + // Token0 -> Backup Token Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: backupToken1.address, + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), + externalId: bn('1'), + }) + + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + minBuyAmt.add(leftoverSellAmt.div(2)) + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(leftoverSellAmt) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Perform Mock Bids (addr1 has balance) + // Pay at worst-case price + await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Check staking situation remains unchanged + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Run auctions - will end current, and will open a new auction for the same amount const leftoverMinBuyAmt = await toMinBuyAmt(leftoverSellAmt, fp('0.5'), fp('1')) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { @@ -2279,14 +2341,17 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { sell: token0.address, buy: backupToken1.address, endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), - externalId: bn('1'), + externalId: bn('2'), }) // Check state expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + minBuyAmt.mul(2) + ) expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt.mul(2)) expect(await rToken.totalSupply()).to.equal(issueAmount) // Check price in USD of the current RToken @@ -2295,7 +2360,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(1, { + await gnosis.placeBid(2, { bidder: addr1.address, sellAmount: leftoverSellAmt, buyAmount: leftoverMinBuyAmt, @@ -2308,11 +2373,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { 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 buyAmtBidRSR: BigNumber = issueAmount.sub(minBuyAmt.add(leftoverMinBuyAmt)).add(1) + // End current auction, should start a new one to sell RSR for collateral + // ~51e18 Tokens left to buy - Sets Buy amount as independent value + const buyAmtBidRSR: BigNumber = issueAmount + .sub(minBuyAmt.mul(2).add(leftoverMinBuyAmt)) + .add(1) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: backingManager, @@ -2342,7 +2407,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { sell: rsr.address, buy: backupToken1.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('2'), + externalId: bn('3'), }) const t = await getTrade(backingManager, rsr.address) @@ -2353,11 +2418,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.add(leftoverMinBuyAmt) + minBuyAmt.mul(2).add(leftoverMinBuyAmt) ) expect(await token0.balanceOf(backingManager.address)).to.equal(0) expect(await backupToken1.balanceOf(backingManager.address)).to.equal( - minBuyAmt.add(leftoverMinBuyAmt) + minBuyAmt.mul(2).add(leftoverMinBuyAmt) ) expect(await rToken.totalSupply()).to.equal(issueAmount) @@ -2370,7 +2435,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(2, { + await gnosis.placeBid(3, { bidder: addr1.address, sellAmount: sellAmtRSR, buyAmount: buyAmtBidRSR, @@ -3246,6 +3311,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { collateral1.address, collateral0.address, issueAmount, + config.minTradeVolume, config.maxTradeSlippage ), bn('1e12') @@ -3309,6 +3375,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { collateral0.address, collateral1.address, issueAmount, + config.minTradeVolume, config.maxTradeSlippage ), bn('1e12') // decimals @@ -4461,10 +4528,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }, ]) - // Check price in USD of the current RToken - should track the capital out on auction + // Check price in USD of the current RToken - capital out on auction await expectRTokenPrice( rTokenAsset.address, - fp('0.625'), + fp('0.5'), ORACLE_ERROR, await backingManager.maxTradeSlippage(), config.minTradeVolume.mul((await assetRegistry.erc20s()).length) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 3858ba4290..4277386931 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -13,7 +13,6 @@ import { CollateralStatus, TradeKind, MAX_UINT192, - ONE_PERIOD, } from '../common/constants' import { expectEvents } from '../common/events' import { bn, divCeil, fp, near } from '../common/numbers' @@ -60,7 +59,6 @@ import { REVENUE_HIDING, ORACLE_ERROR, ORACLE_TIMEOUT, - ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, } from './fixtures' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' @@ -556,7 +554,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) }) - it('Should forward RSR revenue directly to StRSR and call payoutRewards()', async () => { + it('Should forward RSR revenue directly to StRSR', async () => { const amount = bn('2000e18') await rsr.connect(owner).mint(backingManager.address, amount) expect(await rsr.balanceOf(backingManager.address)).to.equal(amount) @@ -564,36 +562,20 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(rsrTrader.address)).to.equal(0) expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(0) - // 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, - }, - ]) - + await expect(backingManager.forwardRevenue([rsr.address])).to.emit(rsr, 'Transfer') expect(await rsr.balanceOf(backingManager.address)).to.equal(0) expect(await rsr.balanceOf(stRSR.address)).to.equal(amount) expect(await rsr.balanceOf(rsrTrader.address)).to.equal(0) expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(0) }) - it('Should launch revenue auction if UNPRICED', async () => { - // After oracleTimeout it should still launch auction for RToken + it('Should launch revenue auction at lotPrice if UNPRICED', async () => { + // After oracleTimeout the lotPrice should be the original price still await advanceTime(ORACLE_TIMEOUT.toString()) await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) await rTokenTrader.callStatic.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) - // After priceTimeout it should not buy RToken + // After oracleTimeout the lotPrice should be the original price still await advanceTime(PRICE_TIMEOUT.toString()) await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) await expect( @@ -669,113 +651,9 @@ 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)) @@ -798,9 +676,6 @@ 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( @@ -1106,7 +981,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { minBuyAmtRToken.div(bn('1e15')) ) }) - it('Should be able to start a dust auction BATCH_AUCTION, if enabled', async () => { const minTrade = bn('1e18') @@ -1162,26 +1036,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 rsrTrader.connect(owner).setMinTradeVolume(minTrade) + await rTokenTrader.connect(owner).setMinTradeVolume(minTrade) const dustAmount = bn('1e17') - await token0.connect(addr1).transfer(rsrTrader.address, dustAmount) + await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) + const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) await setOraclePrice(collateral0.address, bn(0)) await collateral0.refresh() await advanceTime(PRICE_TIMEOUT.add(ORACLE_TIMEOUT).toString()) - await setOraclePrice(rsrAsset.address, bn('1e8')) + await setOraclePrice(collateral1.address, bn('1e8')) - const p = await collateral0.price() + const p = await collateral0.lotPrice() expect(p[0]).to.equal(0) - expect(p[1]).to.equal(MAX_UINT192) + expect(p[1]).to.equal(0) await expect( - 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' - ) + p1RevenueTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) + ).to.revertedWith('bad sell pricing') + await expect( + p1RevenueTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) + ).to.emit(rTokenTrader, 'TradeStarted') }) it('Should not launch an auction for 1 qTok', async () => { @@ -1226,7 +1100,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, bn(606), // 2 qTok auction at $300 (after accounting for price.high) - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) // Set a very high price @@ -1307,7 +1181,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, MAX_UINT192, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -1318,7 +1192,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, rsr.address, MAX_UINT192, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -1486,7 +1360,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -1537,7 +1411,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(99) // due to oracle error + const sellAmt: BigNumber = fp('1').mul(100).div(101) // due to oracle error const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) // Run auctions @@ -1685,7 +1559,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -1719,7 +1593,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(99) // due to high price setting trade size + const sellAmt: BigNumber = fp('1').mul(100).div(101) // due to high price setting trade size const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) await expectEvents(backingManager.claimRewards(), [ @@ -1884,7 +1758,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -1917,7 +1791,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(99) // due to high price setting trade size + const sellAmt: BigNumber = fp('1').mul(100).div(101) // due to high price setting trade size const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) const sellAmtRToken: BigNumber = rewardAmountAAVE.mul(20).div(100) // All Rtokens can be sold - 20% of total comp based on f @@ -2095,6 +1969,10 @@ 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 @@ -2112,10 +1990,6 @@ 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) @@ -2129,370 +2003,57 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await whileImpersonating(backingManager.address, async (bmSigner) => { await rsr.connect(bmSigner).approve(distributor.address, distAmount) - await expect( - distributor.connect(bmSigner).distribute(rsr.address, distAmount) - ).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) - }) - 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()) + await expect( + distributor.connect(bmSigner).distribute(rsr.address, distAmount) + ).to.be.revertedWith('RevenueTraders only') + }) - // 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, + // 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 gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmtRToken, - buyAmount: minBuyAmtRToken, + await whileImpersonating(rsrTrader.address, async (bmSigner) => { + await rsr.connect(bmSigner).approve(distributor.address, distAmount) + await distributor.connect(bmSigner).distribute(rsr.address, distAmount) }) - // Pause Trading - await main.connect(owner).pauseTrading() + // RSR should be in staking pool + expect(await rsr.balanceOf(stRSR.address)).to.equal(distAmount.mul(2)) + }) - // 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, - }, - ]) + 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)) - // 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) + await whileImpersonating(rTokenTrader.address, async (bmSigner) => { + await expect( + distributor.connect(bmSigner).distribute(rsr.address, bn(100)) + ).to.be.revertedWith('nothing to distribute') + }) - expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) - expect(await rToken.balanceOf(furnace.address)).to.equal(bn(0)) + // 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 trade even if price for buy token = 0', async () => { @@ -2683,133 +2244,11 @@ 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 @@ -3388,7 +2827,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: token2.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.05'), delayUntilDefault: await collateral2.delayUntilDefault(), @@ -3515,6 +2954,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rTokenAsset.address, collateral0.address, issueAmount, + config.minTradeVolume, config.maxTradeSlippage ) expect(actual).to.be.closeTo(expected, expected.div(bn('1e15'))) @@ -3574,6 +3014,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rTokenAsset.address, collateral0.address, issueAmount, + config.minTradeVolume, config.maxTradeSlippage ) expect(await rTokenTrader.tradesOpen()).to.equal(0) @@ -4350,106 +3791,6 @@ 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 () { @@ -4590,7 +3931,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 81629b3426..927858718f 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -1,6 +1,5 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { expect } from 'chai' import { signERC2612Permit } from 'eth-permit' import { BigNumber, ContractFactory } from 'ethers' @@ -537,24 +536,6 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { await expect(stRSR.connect(addr1).unstake(0)).to.be.revertedWith('frozen or trading paused') }) - it('Should emit UnstakingStarted event with draftEra -- regression test 01/18/2024', async () => { - const amount: BigNumber = bn('1000e18') - - // Stake - await rsr.connect(addr1).approve(stRSR.address, amount) - await stRSR.connect(addr1).stake(amount) - - // Seize half the RSR, bumping the draftEra because the withdrawal queue is empty - await whileImpersonating(backingManager.address, async (signer) => { - await stRSR.connect(signer).seizeRSR(amount.div(2)) - }) - - // Unstake - await expect(stRSR.connect(addr1).unstake(amount)) - .emit(stRSR, 'UnstakingStarted') - .withArgs(0, 2, addr1.address, amount.div(2), amount, anyValue) - }) - it('Should create Pending withdrawal when unstaking', async () => { const amount: BigNumber = bn('1000e18') @@ -2028,7 +2009,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { await expect(stRSR.connect(addr1).unstake(one)) .emit(stRSR, 'UnstakingStarted') - .withArgs(0, 2, addr1.address, bn(0), one, availableAt) + .withArgs(0, 1, addr1.address, bn(0), one, availableAt) // Check withdrawal properly registered - Check draft era //await expectWithdrawal(addr1.address, 0, { rsrAmount: bn(1) }) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 634c60aac7..fc823d852b 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`] = `366975`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `361087`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `369090`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `363202`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `371228`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `365340`; exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63333`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `453893`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451427`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `543745`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `541279`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `531583`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `529117`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `533721`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `531255`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113028`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113056`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 5318fd2f61..848354f559 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`] = `8330567`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8393668`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464253`; diff --git a/test/__snapshots__/Furnace.test.ts.snap b/test/__snapshots__/Furnace.test.ts.snap index e905f3eec0..af06969a2f 100644 --- a/test/__snapshots__/Furnace.test.ts.snap +++ b/test/__snapshots__/Furnace.test.ts.snap @@ -1,35 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `71626`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83931`; -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `77515`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `89820`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `71626`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83931`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `65998`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `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 4`] = `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 5`] = `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 6`] = `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 7`] = `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 8`] = `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 9`] = `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 10`] = `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 11`] = `65998`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `51726`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64031`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `68358`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80663`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `65998`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `28452`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40761`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 0771900efd..06ba9d68c7 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `393855`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `357705`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `245356`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `195889`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `245356`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `195889`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `224015`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167045`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80510`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80532`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70022`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70044`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index 600063cf88..f50430c9b4 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `782176`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `787453`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `609176`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `614457`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `583880`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `589230`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 1bcce9471c..9e0d532f8e 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1396756`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1384418`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1518120`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1510705`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `750910`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `747331`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1715195`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1680908`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `179696`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1657793`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1613640`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `179696`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1733823`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1702037`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `207769`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 24037c7f9a..81fa8bb746 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`] = `168005`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `164974`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `168058`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165027`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `168058`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165027`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `211655`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208624`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `232408`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `215308`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1044935`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1008567`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `820357`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `773918`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1222455`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1181227`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `368496`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `318685`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `266512`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `786157`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `739718`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `285704`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `242306`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 36ee8b72fa..dbc65bb91d 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -2,7 +2,7 @@ exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `151759`; exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63409`; @@ -14,6 +14,6 @@ exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `606291`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `572011`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `536425`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `526015`; diff --git a/test/fixtures.ts b/test/fixtures.ts index a787359e1b..ff881e60d0 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -1,23 +1,11 @@ import { ContractFactory } from 'ethers' import { expect } from 'chai' -import hre, { ethers, upgrades } from 'hardhat' +import hre, { ethers } from 'hardhat' import { getChainId } from '../common/blockchain-utils' -import { - IConfig, - IImplementations, - IMonitorParams, - IRevenueShare, - networkConfig, -} from '../common/configuration' +import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../common/configuration' import { expectInReceipt } from '../common/events' import { bn, fp } from '../common/numbers' -import { - CollateralStatus, - PAUSER, - LONG_FREEZER, - SHORT_FREEZER, - ZERO_ADDRESS, -} from '../common/constants' +import { CollateralStatus, PAUSER, LONG_FREEZER, SHORT_FREEZER } from '../common/constants' import { Asset, AssetRegistryP1, @@ -36,7 +24,6 @@ import { DutchTrade, FacadeRead, FacadeAct, - FacadeMonitor, FacadeTest, DistributorP1, FiatCollateral, @@ -84,16 +71,14 @@ export const SLOW = !!useEnv('SLOW') export const PRICE_TIMEOUT = bn('604800') // 1 week -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_TIMEOUT = bn('281474976710655').div(2) // type(uint48).max / 2 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.1.0' +export const VERSION = '3.0.1' export type Collateral = | FiatCollateral @@ -198,7 +183,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -218,7 +203,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -238,7 +223,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -267,7 +252,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -295,7 +280,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -425,7 +410,6 @@ export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixt facade: FacadeRead facadeAct: FacadeAct facadeTest: FacadeTest - facadeMonitor: FacadeMonitor broker: TestIBroker rsrTrader: TestIRevenueTrader rTokenTrader: TestIRevenueTrader @@ -482,11 +466,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = }, } - // Setup Monitor Params (mock addrs for local deployment) - const monitorParams: IMonitorParams = { - AAVE_V2_DATA_PROVIDER_ADDR: ZERO_ADDRESS, - } - // Deploy TradingLib external library const TradingLibFactory: ContractFactory = await ethers.getContractFactory('TradingLibP0') const tradingLib: TradingLibP0 = await TradingLibFactory.deploy() @@ -503,19 +482,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = const FacadeTestFactory: ContractFactory = await ethers.getContractFactory('FacadeTest') const facadeTest = await FacadeTestFactory.deploy() - // Deploy FacadeMonitor - const FacadeMonitorFactory: ContractFactory = await ethers.getContractFactory('FacadeMonitor') - - const facadeMonitor = await upgrades.deployProxy( - FacadeMonitorFactory, - [owner.address], - { - kind: 'uups', - initializer: 'init', - constructorArgs: [monitorParams], - } - ) - // Deploy RSR chainlink feed const MockV3AggregatorFactory: ContractFactory = await ethers.getContractFactory( 'MockV3Aggregator' @@ -533,7 +499,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) await rsrAsset.refresh() @@ -665,7 +631,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, aaveToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) await aaveAsset.refresh() @@ -680,7 +646,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) await compAsset.refresh() @@ -783,7 +749,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = facade, facadeAct, facadeTest, - facadeMonitor, rsrTrader, rTokenTrader, bySymbol, diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 71ae4bf11d..7e067fa875 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -8,7 +8,6 @@ import { IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, - ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -20,14 +19,7 @@ import { expectEvents } from '../../common/events' import { bn, fp, toBNDecimals } from '../../common/numbers' import { advanceBlocks, advanceTime } from '../utils/time' import { whileImpersonating } from '../utils/impersonation' -import { - expectDecayedPrice, - expectExactPrice, - expectPrice, - expectRTokenPrice, - expectUnpriced, - setOraclePrice, -} from '../utils/oracles' +import { expectPrice, expectRTokenPrice, expectUnpriced, setOraclePrice } from '../utils/oracles' import forkBlockNumber from './fork-block-numbers' import { Asset, @@ -47,7 +39,6 @@ import { MockV3Aggregator, NonFiatCollateral, RTokenAsset, - SelfReferentialCollateral, StaticATokenLM, TestIBackingManager, TestIBasketHandler, @@ -1091,7 +1082,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, }) it('Should handle invalid/stale Price - Assets', async () => { - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) // Stale Oracle await expectUnpriced(compAsset.address) @@ -1123,30 +1114,19 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, ORACLE_ERROR, networkConfig[chainId].tokens.stkAAVE || '', config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + MAX_ORACLE_TIMEOUT ) ) - await setOraclePrice(zeroPriceAsset.address, bn('1e10')) - await zeroPriceAsset.refresh() - - const initialPrice = await zeroPriceAsset.price() - await setOraclePrice(zeroPriceAsset.address, bn(0)) - await expectExactPrice(zeroPriceAsset.address, initialPrice) - // After oracle timeout, begins decay - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) await setOraclePrice(zeroPriceAsset.address, bn(0)) - await expectDecayedPrice(zeroPriceAsset.address) - // After price timeout, unpriced - await advanceTime(PRICE_TIMEOUT.toString()) - await setOraclePrice(zeroPriceAsset.address, bn(0)) + // Unpriced await expectUnpriced(zeroPriceAsset.address) }) it('Should handle invalid/stale Price - Collateral - Fiat', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) await expectUnpriced(daiCollateral.address) await expectUnpriced(usdcCollateral.address) @@ -1203,30 +1183,19 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: dai.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }) - await setOraclePrice(zeroFiatCollateral.address, bn('1e8')) await zeroFiatCollateral.refresh() - expect(await zeroFiatCollateral.status()).to.equal(CollateralStatus.SOUND) - const initialPrice = await zeroFiatCollateral.price() await setOraclePrice(zeroFiatCollateral.address, bn(0)) - await expectExactPrice(zeroFiatCollateral.address, initialPrice) - // 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)) + // Unpriced await expectUnpriced(zeroFiatCollateral.address) - // Marked IFFY after refresh + // Refresh should mark status IFFY await zeroFiatCollateral.refresh() expect(await zeroFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -1237,7 +1206,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await cUsdtCollateral.status()).to.equal(CollateralStatus.SOUND) // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) // Compound await expectUnpriced(cDaiCollateral.address) @@ -1289,29 +1258,18 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, REVENUE_HIDING ) - await setOraclePrice(zeropriceCtokenCollateral.address, bn('1e8')) await zeropriceCtokenCollateral.refresh() - expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.SOUND) - - const initialPrice = await zeropriceCtokenCollateral.price() - await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) - await expectExactPrice(zeropriceCtokenCollateral.address, initialPrice) - // After oracle timeout, begins decay - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) - await expectDecayedPrice(zeropriceCtokenCollateral.address) - // After price timeout, unpriced - await advanceTime(PRICE_TIMEOUT.toString()) - await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) + // Unpriced await expectUnpriced(zeropriceCtokenCollateral.address) // Refresh should mark status IFFY @@ -1321,7 +1279,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, it('Should handle invalid/stale Price - Collateral - ATokens Fiat', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) // Aave await expectUnpriced(aDaiCollateral.address) @@ -1377,29 +1335,18 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: stataDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, REVENUE_HIDING ) - await setOraclePrice(zeroPriceAtokenCollateral.address, bn('1e8')) await zeroPriceAtokenCollateral.refresh() - expect(await zeroPriceAtokenCollateral.status()).to.equal(CollateralStatus.SOUND) - - const initialPrice = await zeroPriceAtokenCollateral.price() - await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) - await expectExactPrice(zeroPriceAtokenCollateral.address, initialPrice) - // After oracle timeout, begins decay - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) - await expectDecayedPrice(zeroPriceAtokenCollateral.address) - // After price timeout, unpriced - await advanceTime(PRICE_TIMEOUT.toString()) - await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) + // Unpriced await expectUnpriced(zeroPriceAtokenCollateral.address) // Refresh should mark status IFFY @@ -1409,7 +1356,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, it('Should handle invalid/stale Price - Collateral - Non-Fiatcoins', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) // Aave await expectUnpriced(wbtcCollateral.address) @@ -1456,35 +1403,32 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: wbtc.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - ORACLE_TIMEOUT_PRE_BUFFER + MAX_ORACLE_TIMEOUT ) - await setOraclePrice(zeroPriceNonFiatCollateral.address, bn('1e10')) await zeroPriceNonFiatCollateral.refresh() - const initialPrice = await zeroPriceNonFiatCollateral.price() - await setOraclePrice(zeroPriceNonFiatCollateral.address, bn(0)) - await expectExactPrice(zeroPriceNonFiatCollateral.address, initialPrice) - - // After oracle timeout, begins decay - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) - await setOraclePrice(zeroPriceNonFiatCollateral.address, bn(0)) - await expectDecayedPrice(zeroPriceNonFiatCollateral.address) + // Set price = 0 + const chainlinkFeedAddr = await zeroPriceNonFiatCollateral.chainlinkFeed() + const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) + await v3Aggregator.updateAnswer(bn(0)) - // After price timeout, unpriced - await advanceTime(PRICE_TIMEOUT.toString()) - await setOraclePrice(zeroPriceNonFiatCollateral.address, bn(0)) + // Unpriced await expectUnpriced(zeroPriceNonFiatCollateral.address) + + // Refresh should mark status IFFY + await zeroPriceNonFiatCollateral.refresh() + expect(await zeroPriceNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) it('Should handle invalid/stale Price - Collateral - CTokens Non-Fiat', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) // Compound await expectUnpriced(cWBTCCollateral.address) @@ -1536,39 +1480,36 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cWBTCVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - ORACLE_TIMEOUT_PRE_BUFFER, + MAX_ORACLE_TIMEOUT, REVENUE_HIDING ) ) - await setOraclePrice(zeropriceCtokenNonFiatCollateral.address, bn('1e10')) await zeropriceCtokenNonFiatCollateral.refresh() - const initialPrice = await zeropriceCtokenNonFiatCollateral.price() - await setOraclePrice(zeropriceCtokenNonFiatCollateral.address, bn(0)) - await expectExactPrice(zeropriceCtokenNonFiatCollateral.address, initialPrice) - - // After oracle timeout, begins decay - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) - await setOraclePrice(zeropriceCtokenNonFiatCollateral.address, bn(0)) - await expectDecayedPrice(zeropriceCtokenNonFiatCollateral.address) + // Set price = 0 + const chainlinkFeedAddr = await zeropriceCtokenNonFiatCollateral.targetUnitChainlinkFeed() + const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) + await v3Aggregator.updateAnswer(bn(0)) - // After price timeout, unpriced - await advanceTime(PRICE_TIMEOUT.toString()) - await setOraclePrice(zeropriceCtokenNonFiatCollateral.address, bn(0)) + // Unpriced await expectUnpriced(zeropriceCtokenNonFiatCollateral.address) + + // Refresh should mark status IFFY + await zeropriceCtokenNonFiatCollateral.refresh() + expect(await zeropriceCtokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) 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.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) // Aave await expectUnpriced(wethCollateral.address) @@ -1577,10 +1518,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await wethCollateral.status()).to.equal(CollateralStatus.IFFY) // Self referential collateral with no price - const nonpriceSelfReferentialCollateral: SelfReferentialCollateral = < - SelfReferentialCollateral - >await ( - await ethers.getContractFactory('SelfReferentialCollateral') + const nonpriceSelfReferentialCollateral: FiatCollateral = await ( + await ethers.getContractFactory('FiatCollateral') ).deploy({ priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, @@ -1601,40 +1540,28 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await nonpriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) // Self referential collateral with zero price - const zeroPriceSelfReferentialCollateral: SelfReferentialCollateral = < - SelfReferentialCollateral - >await ( - await ethers.getContractFactory('SelfReferentialCollateral') + const zeroPriceSelfReferentialCollateral: FiatCollateral = await ( + await ethers.getContractFactory('FiatCollateral') ).deploy({ priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, erc20: weth.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn('0'), delayUntilDefault, }) - await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn('1e10')) await zeroPriceSelfReferentialCollateral.refresh() - expect(await zeroPriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) - - const initialPrice = await zeroPriceSelfReferentialCollateral.price() - await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) - await expectExactPrice(zeroPriceSelfReferentialCollateral.address, initialPrice) - // After oracle timeout, begins decay - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + // Set price = 0 await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) - await expectDecayedPrice(zeroPriceSelfReferentialCollateral.address) - // After price timeout, unpriced - await advanceTime(PRICE_TIMEOUT.toString()) - await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) + // Unpriced await expectUnpriced(zeroPriceSelfReferentialCollateral.address) - // Refresh should mark status DISABLED + // Refresh should mark status IFFY await zeroPriceSelfReferentialCollateral.refresh() expect(await zeroPriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -1643,7 +1570,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const delayUntilDefault = bn('86400') // 24h // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) // Compound await expectUnpriced(cETHCollateral.address) @@ -1694,7 +1621,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cETHVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn('0'), delayUntilDefault, @@ -1702,24 +1629,12 @@ 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 - ) - const initialPrice = await zeroPriceCtokenSelfReferentialCollateral.price() + // Set price = 0 await setOraclePrice(zeroPriceCtokenSelfReferentialCollateral.address, bn(0)) - await expectExactPrice(zeroPriceCtokenSelfReferentialCollateral.address, initialPrice) - // 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)) + // Unpriced await expectUnpriced(zeroPriceCtokenSelfReferentialCollateral.address) // Refresh should mark status IFFY @@ -1731,7 +1646,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, it('Should handle invalid/stale Price - Collateral - EUR Fiat', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) await expectUnpriced(eurtCollateral.address) @@ -1777,30 +1692,22 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: eurt.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - ORACLE_TIMEOUT_PRE_BUFFER + MAX_ORACLE_TIMEOUT ) - await setOraclePrice(invalidPriceEURCollateral.address, bn('1e10')) await invalidPriceEURCollateral.refresh() - expect(await invalidPriceEURCollateral.status()).to.equal(CollateralStatus.SOUND) - - const initialPrice = await invalidPriceEURCollateral.price() - await setOraclePrice(invalidPriceEURCollateral.address, bn(0)) - await expectExactPrice(invalidPriceEURCollateral.address, initialPrice) - // After oracle timeout, begins decay - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) - await setOraclePrice(invalidPriceEURCollateral.address, bn(0)) - await expectDecayedPrice(invalidPriceEURCollateral.address) + // Set price = 0 + const chainlinkFeedAddr = await invalidPriceEURCollateral.targetUnitChainlinkFeed() + const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) + await v3Aggregator.updateAnswer(bn(0)) - // After price timeout, unpriced - await advanceTime(PRICE_TIMEOUT.toString()) - await setOraclePrice(invalidPriceEURCollateral.address, bn(0)) + // With zero price await expectUnpriced(invalidPriceEURCollateral.address) // Refresh should mark status IFFY diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 355d92f00c..3d63f9e5b8 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -551,11 +551,10 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function }) it('should be able to scoop entire auction cheaply when minBuyAmount = 0', async () => { - // Make collateral0 price (0, FIX_MAX) + // Make collateral0 lotPrice (0, 0) 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 @@ -753,7 +752,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function oracleError: ORACLE_ERROR, // shouldn't matter erc20: sellTok.address, maxTradeVolume: MAX_UINT192, - oracleTimeout: MAX_UINT48.sub(300), + oracleTimeout: MAX_UINT48, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), // shouldn't matter delayUntilDefault: bn('604800'), // shouldn't matter @@ -765,7 +764,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function oracleError: ORACLE_ERROR, // shouldn't matter erc20: buyTok.address, maxTradeVolume: MAX_UINT192, - oracleTimeout: MAX_UINT48.sub(300), + oracleTimeout: MAX_UINT48, 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 new file mode 100644 index 0000000000..a9ec5a85ce --- /dev/null +++ b/test/integration/__snapshots__/CTokenVaultGas.test.ts.snap @@ -0,0 +1,25 @@ +// 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 c9778bb7df..f22f9a1f1e 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -1,14 +1,8 @@ import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' import { getChainId } from '../../common/blockchain-utils' -import { - IConfig, - IImplementations, - IMonitorParams, - IRevenueShare, - networkConfig, -} from '../../common/configuration' -import { PAUSER, SHORT_FREEZER, LONG_FREEZER, ZERO_ADDRESS } from '../../common/constants' +import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../../common/configuration' +import { PAUSER, SHORT_FREEZER, LONG_FREEZER } from '../../common/constants' import { expectInReceipt } from '../../common/events' import { advanceTime } from '../utils/time' import { bn, fp } from '../../common/numbers' @@ -60,14 +54,13 @@ import { TestIRToken, TestIStRSR, RecollateralizationLibP1, - FacadeMonitor, } from '../../typechain' import { Collateral, Implementation, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -197,7 +190,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -226,7 +219,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -254,7 +247,6 @@ export async function collateralFixture( 'stat' + symbol ) ) - const coll = await ATokenCollateralFactory.deploy( { priceTimeout: PRICE_TIMEOUT, @@ -262,7 +254,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: staticErc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -288,13 +280,13 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold, delayUntilDefault, }, targetUnitOracleAddr, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) await coll.refresh() return [erc20, coll] @@ -322,13 +314,13 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold, delayUntilDefault, }, targetUnitOracleAddr, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, REVENUE_HIDING ) await coll.refresh() @@ -347,7 +339,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold: bn(0), delayUntilDefault, @@ -379,7 +371,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold: bn(0), delayUntilDefault, @@ -407,13 +399,13 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold, delayUntilDefault, }, targetUnitOracleAddr, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) await coll.refresh() return [erc20, coll] @@ -592,7 +584,7 @@ type RSRAndCompAaveAndCollateralAndModuleFixture = RSRFixture & CollateralFixture & ModuleFixture -export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixture { +interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixture { config: IConfig dist: IRevenueShare deployer: TestIDeployer @@ -611,7 +603,6 @@ export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixt facade: FacadeRead facadeAct: FacadeAct facadeTest: FacadeTest - facadeMonitor: FacadeMonitor broker: TestIBroker rsrTrader: TestIRevenueTrader rTokenTrader: TestIRevenueTrader @@ -672,11 +663,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = }, } - // Setup Monitor Params based on network - const monitorParams: IMonitorParams = { - AAVE_V2_DATA_PROVIDER_ADDR: networkConfig[chainId].AAVE_DATA_PROVIDER ?? ZERO_ADDRESS, - } - // Deploy FacadeRead const FacadeReadFactory: ContractFactory = await ethers.getContractFactory('FacadeRead') const facade = await FacadeReadFactory.deploy() @@ -689,10 +675,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = const FacadeTestFactory: ContractFactory = await ethers.getContractFactory('FacadeTest') const facadeTest = await FacadeTestFactory.deploy() - // Deploy FacadeMonitor - Use implementation to simplify deployments - const FacadeMonitorFactory: ContractFactory = await ethers.getContractFactory('FacadeMonitor') - const facadeMonitor = await FacadeMonitorFactory.deploy(monitorParams) - // Deploy TradingLib external library const TradingLibFactory: ContractFactory = await ethers.getContractFactory( 'RecollateralizationLibP1' @@ -714,7 +696,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) await rsrAsset.refresh() @@ -838,7 +820,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, aaveToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) await aaveAsset.refresh() @@ -852,7 +834,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) await compAsset.refresh() @@ -948,7 +930,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = facade, facadeAct, facadeTest, - facadeMonitor, rsrTrader, rTokenTrader, } diff --git a/test/integration/fork-block-numbers.ts b/test/integration/fork-block-numbers.ts index f5b5dff068..c575f48e3d 100644 --- a/test/integration/fork-block-numbers.ts +++ b/test/integration/fork-block-numbers.ts @@ -5,7 +5,6 @@ const forkBlockNumber = { 'mainnet-deployment': 15690042, // Ethereum 'flux-finance': 16836855, // Ethereum 'mainnet-2.0': 17522362, // Ethereum - 'facade-monitor': 18742016, // Ethereum default: 18522901, // Ethereum } diff --git a/test/monitor/FacadeMonitor.test.ts b/test/monitor/FacadeMonitor.test.ts deleted file mode 100644 index 45b7bf1d22..0000000000 --- a/test/monitor/FacadeMonitor.test.ts +++ /dev/null @@ -1,1417 +0,0 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { expect } from 'chai' -import { BigNumber, Contract } from 'ethers' -import hre, { ethers } from 'hardhat' -import { Collateral, IMPLEMENTATION } from '../fixtures' -import { defaultFixtureNoBasket, DefaultFixture } from '../integration/fixtures' -import { getChainId } from '../../common/blockchain-utils' -import { IConfig, baseL2Chains, networkConfig } from '../../common/configuration' -import { bn, fp, toBNDecimals } from '../../common/numbers' -import { advanceTime } from '../utils/time' -import { whileImpersonating } from '../utils/impersonation' -import { pushOracleForward } from '../utils/oracles' - -import forkBlockNumber from '../integration/fork-block-numbers' -import { - ATokenFiatCollateral, - AaveV3FiatCollateral, - CTokenV3Collateral, - CTokenFiatCollateral, - ERC20Mock, - FacadeTest, - FacadeMonitor, - FiatCollateral, - IAToken, - IComptroller, - IERC20, - ILendingPool, - IPool, - IWETH, - StaticATokenLM, - IAssetRegistry, - TestIBackingManager, - TestIBasketHandler, - TestICToken, - TestIRToken, - USDCMock, - CTokenWrapper, - StaticATokenV3LM, - CusdcV3Wrapper, - CometInterface, - StargateRewardableWrapper, - StargatePoolFiatCollateral, - IStargatePool, - MorphoAaveV2TokenisedDeposit, -} from '../../typechain' -import { useEnv } from '#/utils/env' -import { MAX_UINT256 } from '#/common/constants' - -enum CollPluginType { - AAVE_V2, - AAVE_V3, - COMPOUND_V2, - COMPOUND_V3, - STARGATE, - FLUX, - MORPHO_AAVE_V2, -} - -// Relevant addresses (Mainnet) -const holderDAI = '0x075e72a5eDf65F0A5f44699c7654C1a76941Ddc8' -const holderCDAI = '0x01d127D90513CCB6071F83eFE15611C4d9890668' -const holderADAI = '0x07edE94cF6316F4809f2B725f5d79AD303fB4Dc8' -const holderaUSDCV3 = '0x1eAb3B222A5B57474E0c237E7E1C4312C1066855' -const holderWETH = '0xF04a5cC80B1E94C69B48f5ee68a08CD2F09A7c3E' -const holdercUSDCV3 = '0x7f714b13249BeD8fdE2ef3FBDfB18Ed525544B03' -const holdersUSDC = '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b' -const holderfUSDC = '0x86A07dDED024121b282362f4e7A249b00F5dAB37' -const holderUSDC = '0x28C6c06298d514Db089934071355E5743bf21d60' - -let owner: SignerWithAddress - -const describeFork = useEnv('FORK') ? describe : describe.skip - -describeFork(`FacadeMonitor - Integration - Mainnet Forking P${IMPLEMENTATION}`, function () { - let addr1: SignerWithAddress - let addr2: SignerWithAddress - - // Assets - let collateral: Collateral[] - - // Tokens and Assets - let dai: ERC20Mock - let aDai: IAToken - let stataDai: StaticATokenLM - let usdc: USDCMock - let aUsdcV3: IAToken - let sUsdc: IStargatePool - let fUsdc: TestICToken - let weth: IWETH - let cDai: TestICToken - let cDaiVault: CTokenWrapper - let cusdcV3: CometInterface - let daiCollateral: FiatCollateral - let aDaiCollateral: ATokenFiatCollateral - - // Contracts to retrieve after deploy - let rToken: TestIRToken - let facadeTest: FacadeTest - let facadeMonitor: FacadeMonitor - let assetRegistry: IAssetRegistry - let basketHandler: TestIBasketHandler - let backingManager: TestIBackingManager - let config: IConfig - - let initialBal: BigNumber - let basket: Collateral[] - let erc20s: IERC20[] - - let fullLiquidityAmt: BigNumber - let chainId: number - - // Setup test environment - const setup = async (blockNumber: number) => { - // Use Mainnet fork - await hre.network.provider.request({ - method: 'hardhat_reset', - params: [ - { - forking: { - jsonRpcUrl: useEnv('MAINNET_RPC_URL'), - blockNumber: blockNumber, - }, - }, - ], - }) - } - - describe('FacadeMonitor', () => { - before(async () => { - await setup(forkBlockNumber['facade-monitor']) - - chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - }) - - beforeEach(async () => { - ;[owner, addr1, addr2] = await ethers.getSigners() - ;({ - erc20s, - collateral, - basket, - assetRegistry, - basketHandler, - backingManager, - rToken, - facadeTest, - facadeMonitor, - config, - } = await loadFixture(defaultFixtureNoBasket)) - - // Get tokens - dai = erc20s[0] // DAI - cDaiVault = erc20s[6] // cDAI - cDai = await ethers.getContractAt('TestICToken', await cDaiVault.underlying()) // cDAI - stataDai = erc20s[10] // static aDAI - - // Get plain aTokens - aDai = ( - await ethers.getContractAt( - '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', - networkConfig[chainId].tokens.aDAI || '' - ) - ) - - // Get collaterals - daiCollateral = collateral[0] // DAI - aDaiCollateral = collateral[10] // aDAI - - // Get assets and tokens for default basket - daiCollateral = basket[0] - aDaiCollateral = basket[1] - - dai = await ethers.getContractAt('ERC20Mock', await daiCollateral.erc20()) - stataDai = ( - await ethers.getContractAt('StaticATokenLM', await aDaiCollateral.erc20()) - ) - - // Get plain aToken - aDai = ( - await ethers.getContractAt( - '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', - networkConfig[chainId].tokens.aDAI || '' - ) - ) - - usdc = ( - await ethers.getContractAt('USDCMock', networkConfig[chainId].tokens.USDC || '') - ) - aUsdcV3 = await ethers.getContractAt( - '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', // use V2 interface, it includes ERC20 - networkConfig[chainId].tokens.aEthUSDC || '' - ) - - cusdcV3 = ( - await ethers.getContractAt('CometInterface', networkConfig[chainId].tokens.cUSDCv3 || '') - ) - - sUsdc = ( - await ethers.getContractAt('IStargatePool', networkConfig[chainId].tokens.sUSDC || '') - ) - - fUsdc = ( - await ethers.getContractAt('TestICToken', networkConfig[chainId].tokens.fUSDC || '') - ) - - initialBal = bn('2500000e18') - - // Fund user with static aDAI - await whileImpersonating(holderADAI, async (adaiSigner) => { - // Wrap ADAI into static ADAI - await aDai.connect(adaiSigner).transfer(addr1.address, initialBal) - await aDai.connect(addr1).approve(stataDai.address, initialBal) - await stataDai.connect(addr1).deposit(addr1.address, initialBal, 0, false) - }) - - // Fund user with aUSDCV3 - await whileImpersonating(holderaUSDCV3, async (ausdcV3Signer) => { - await aUsdcV3.connect(ausdcV3Signer).transfer(addr1.address, toBNDecimals(initialBal, 6)) - }) - - // Fund user with DAI - await whileImpersonating(holderDAI, async (daiSigner) => { - await dai.connect(daiSigner).transfer(addr1.address, initialBal.mul(8)) - }) - - // Fund user with cDAI - await whileImpersonating(holderCDAI, async (cdaiSigner) => { - await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) - await cDai.connect(addr1).approve(cDaiVault.address, toBNDecimals(initialBal, 8).mul(100)) - await cDaiVault.connect(addr1).deposit(toBNDecimals(initialBal, 8).mul(100), addr1.address) - }) - - // Fund user with cUSDCV3 - await whileImpersonating(holdercUSDCV3, async (cusdcV3Signer) => { - await cusdcV3.connect(cusdcV3Signer).transfer(addr1.address, toBNDecimals(initialBal, 6)) - }) - - // Fund user with sUSDC - await whileImpersonating(holdersUSDC, async (susdcSigner) => { - await sUsdc.connect(susdcSigner).transfer(addr1.address, toBNDecimals(initialBal, 6)) - }) - - // Fund user with fUSDC - await whileImpersonating(holderfUSDC, async (fusdcSigner) => { - await fUsdc - .connect(fusdcSigner) - .transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) - }) - - // Fund user with USDC - await whileImpersonating(holderUSDC, async (usdcSigner) => { - await usdc.connect(usdcSigner).transfer(addr1.address, toBNDecimals(initialBal, 6)) - }) - - // Fund user with WETH - weth = await ethers.getContractAt('IWETH', networkConfig[chainId].tokens.WETH || '') - await whileImpersonating(holderWETH, async (signer) => { - await weth.connect(signer).transfer(addr1.address, fp('500000')) - }) - }) - - describe('AAVE V2', () => { - const issueAmount: BigNumber = bn('1000000e18') - let lendingPool: ILendingPool - let aaveV2DataProvider: Contract - - beforeEach(async () => { - // Setup basket - await basketHandler.connect(owner).setPrimeBasket([stataDai.address], [fp('1')]) - await basketHandler.connect(owner).refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - - // Provide approvals - await stataDai.connect(addr1).approve(rToken.address, issueAmount) - - // Advance time significantly - Recharge throttle - await advanceTime(100000) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - - lendingPool = ( - await ethers.getContractAt('ILendingPool', networkConfig[chainId].AAVE_LENDING_POOL || '') - ) - - const aaveV2DataProviderAbi = [ - 'function getReserveData(address asset) external view returns (uint256 availableLiquidity,uint256 totalStableDebt,uint256 totalVariableDebt,uint256 liquidityRate,uint256 variableBorrowRate,uint256 stableBorrowRate,uint256 averageStableBorrowRate,uint256 liquidityIndex,uint256 variableBorrowIndex,uint40 lastUpdateTimestamp)', - ] - aaveV2DataProvider = await ethers.getContractAt( - aaveV2DataProviderAbi, - networkConfig[chainId].AAVE_DATA_PROVIDER || '' - ) - - // Get current liquidity - ;[fullLiquidityAmt, , , , , , , , ,] = await aaveV2DataProvider - .connect(addr1) - .getReserveData(dai.address) - - // Provide liquidity in AAVE V2 to be able to borrow - const amountToDeposit = fp('500000') - await weth.connect(addr1).approve(lendingPool.address, amountToDeposit) - await lendingPool.connect(addr1).deposit(weth.address, amountToDeposit, addr1.address, 0) - }) - - it('Should return 100% when full liquidity available', async function () { - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) - - // AAVE V2 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V2, - stataDai.address - ) - ).to.equal(fp('1')) - - // Confirm all can be redeemed - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await stataDai.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await stataDai.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await stataDai.connect(addr2).withdraw(addr2.address, bmBalanceAmt, false) - await expect(lendingPool.connect(addr2).withdraw(dai.address, MAX_UINT256, addr2.address)) - .to.not.be.reverted - expect(await dai.balanceOf(addr2.address)).to.be.gt(bn(0)) - expect(await aDai.balanceOf(addr2.address)).to.equal(bn(0)) - }) - - it('Should return backing redeemable percent correctly', async function () { - // AAVE V2 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V2, - stataDai.address - ) - ).to.equal(fp('1')) - - // Leave only 80% of backing available to be redeemed - const borrowAmount = fullLiquidityAmt.sub(issueAmount.mul(80).div(100)) - await lendingPool.connect(addr1).borrow(dai.address, borrowAmount, 2, 0, addr1.address) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V2, - stataDai.address - ) - ).to.be.closeTo(fp('0.80'), fp('0.01')) - - // Borrow half of the remaining liquidity - const remainingLiquidity = fullLiquidityAmt.sub(borrowAmount) - await lendingPool - .connect(addr1) - .borrow(dai.address, remainingLiquidity.div(2), 2, 0, addr1.address) - - // Now only 40% is available to be redeemed - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V2, - stataDai.address - ) - ).to.be.closeTo(fp('0.40'), fp('0.01')) - - // Confirm we cannot redeem full balance - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await stataDai.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await stataDai.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await stataDai.connect(addr2).withdraw(addr2.address, bmBalanceAmt, false) - await expect(lendingPool.connect(addr2).withdraw(dai.address, MAX_UINT256, addr2.address)) - .to.be.reverted - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - - // But we can redeem if we reduce the amount to 30% - await expect( - lendingPool - .connect(addr2) - .withdraw( - dai.address, - (await aDai.balanceOf(addr2.address)).mul(30).div(100), - addr2.address - ) - ).to.not.be.reverted - expect(await dai.balanceOf(addr2.address)).to.be.gt(0) - }) - - it('Should handle no liquidity', async function () { - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V2, - stataDai.address - ) - ).to.equal(fp('1')) - - // Borrow full liquidity - await lendingPool.connect(addr1).borrow(dai.address, fullLiquidityAmt, 2, 0, addr1.address) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V2, - stataDai.address - ) - ).to.be.closeTo(fp('0'), fp('0.01')) - - // Confirm we cannot redeem anything, not even 1% - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await stataDai.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await stataDai.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await stataDai.connect(addr2).withdraw(addr2.address, bmBalanceAmt, false) - await expect( - lendingPool - .connect(addr2) - .withdraw(dai.address, (await aDai.balanceOf(addr2.address)).div(100), addr2.address) - ).to.be.reverted - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - }) - }) - - describe('AAVE V3', () => { - const issueAmount: BigNumber = bn('1000000e18') - let stataUsdcV3: StaticATokenV3LM - let pool: IPool - - beforeEach(async () => { - const StaticATokenFactory = await hre.ethers.getContractFactory('StaticATokenV3LM') - stataUsdcV3 = await StaticATokenFactory.deploy( - networkConfig[chainId].AAVE_V3_POOL!, - networkConfig[chainId].AAVE_V3_INCENTIVES_CONTROLLER! - ) - - await stataUsdcV3.deployed() - await ( - await stataUsdcV3.initialize( - networkConfig[chainId].tokens.aEthUSDC!, - 'Static Aave Ethereum USDC', - 'saEthUSDC' - ) - ).wait() - - /******** Deploy Aave V3 USDC collateral plugin **************************/ - const usdcOracleTimeout = '86400' // 24 hr - const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% - - const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') - const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') - const collateral = await CollateralFactory.connect(owner).deploy( - { - priceTimeout: bn('604800'), - chainlinkFeed: chainlinkFeed.address, - oracleError: usdcOracleError, - erc20: stataUsdcV3.address, - maxTradeVolume: fp('1e6'), - oracleTimeout: usdcOracleTimeout, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.01').add(usdcOracleError), - delayUntilDefault: bn('86400'), - }, - fp('1e-6') - ) - - // Register and update collateral - await collateral.deployed() - await (await collateral.refresh()).wait() - await pushOracleForward(chainlinkFeed.address) - await assetRegistry.connect(owner).register(collateral.address) - - // Wrap aUsdcV3 - await aUsdcV3.connect(addr1).approve(stataUsdcV3.address, toBNDecimals(initialBal, 6)) - await stataUsdcV3 - .connect(addr1) - ['deposit(uint256,address,uint16,bool)']( - toBNDecimals(initialBal, 6), - addr1.address, - 0, - false - ) - - // Get current liquidity - fullLiquidityAmt = await usdc.balanceOf(aUsdcV3.address) - - // Setup basket - await pushOracleForward(chainlinkFeed.address) - await basketHandler.connect(owner).setPrimeBasket([stataUsdcV3.address], [fp('1')]) - await basketHandler.connect(owner).refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - - // Provide approvals - await stataUsdcV3.connect(addr1).approve(rToken.address, issueAmount) - - // Advance time significantly - Recharge throttle - await advanceTime(100000) - await pushOracleForward(chainlinkFeed.address) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - - pool = await ethers.getContractAt('IPool', networkConfig[chainId].AAVE_V3_POOL || '') - - // Provide liquidity to be able to borrow - const amountToDeposit = fp('500000') - await weth.connect(addr1).approve(pool.address, amountToDeposit) - await pool.connect(addr1).supply(weth.address, amountToDeposit, addr1.address, 0) - }) - - it('Should return 100% when full liquidity available', async function () { - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) - - // AAVE V3 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V3, - stataUsdcV3.address - ) - ).to.equal(fp('1')) - - // Confirm all can be redeemed - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await stataUsdcV3.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await stataUsdcV3.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await stataUsdcV3 - .connect(addr2) - ['redeem(uint256,address,address,bool)']( - bmBalanceAmt, - addr2.address, - addr2.address, - false - ) - await expect(pool.connect(addr2).withdraw(usdc.address, MAX_UINT256, addr2.address)).to.not - .be.reverted - expect(await usdc.balanceOf(addr2.address)).to.be.gt(bn(0)) - expect(await aUsdcV3.balanceOf(addr2.address)).to.equal(bn(0)) - }) - - it('Should return backing redeemable percent correctly', async function () { - // AAVE V3 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V3, - stataUsdcV3.address - ) - ).to.equal(fp('1')) - - // Leave only 80% of backing to be able to be redeemed - const borrowAmount = fullLiquidityAmt.sub(toBNDecimals(issueAmount, 6).mul(80).div(100)) - await pool.connect(addr1).borrow(usdc.address, borrowAmount, 2, 0, addr1.address) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V3, - stataUsdcV3.address - ) - ).to.be.closeTo(fp('0.80'), fp('0.01')) - - // Borrow half of the remaining liquidity - const remainingLiquidity = fullLiquidityAmt.sub(borrowAmount) - await pool - .connect(addr1) - .borrow(usdc.address, remainingLiquidity.div(2), 2, 0, addr1.address) - - // Only 40% available to be redeemed - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V3, - stataUsdcV3.address - ) - ).to.be.closeTo(fp('0.40'), fp('0.01')) - - // Confirm we cannot redeem full balance - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await stataUsdcV3.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await stataUsdcV3.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await stataUsdcV3 - .connect(addr2) - ['redeem(uint256,address,address,bool)']( - bmBalanceAmt, - addr2.address, - addr2.address, - false - ) - await expect(pool.connect(addr2).withdraw(usdc.address, MAX_UINT256, addr2.address)).to.be - .reverted - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - - // We can redeem if we reduce to 30% - await expect( - pool - .connect(addr2) - .withdraw( - usdc.address, - (await aUsdcV3.balanceOf(addr2.address)).mul(30).div(100), - addr2.address - ) - ).to.not.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.be.gt(0) - }) - - it('Should handle no liquidity', async function () { - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V3, - stataUsdcV3.address - ) - ).to.equal(fp('1')) - - // Borrow full liquidity - await pool.connect(addr1).borrow(usdc.address, fullLiquidityAmt, 2, 0, addr1.address) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.AAVE_V3, - stataUsdcV3.address - ) - ).to.be.closeTo(fp('0'), fp('0.01')) - - // Confirm we cannot redeem anything, not even 1% - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await stataUsdcV3.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await stataUsdcV3.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await stataUsdcV3 - .connect(addr2) - ['redeem(uint256,address,address,bool)']( - bmBalanceAmt, - addr2.address, - addr2.address, - false - ) - await expect( - pool - .connect(addr2) - .withdraw( - usdc.address, - (await aUsdcV3.balanceOf(addr2.address)).div(100), - addr2.address - ) - ).to.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - }) - }) - - describe('Compound V2', () => { - const issueAmount: BigNumber = bn('1000000e18') - let comptroller: IComptroller - - beforeEach(async () => { - // Setup basket - await basketHandler.connect(owner).setPrimeBasket([cDaiVault.address], [fp('1')]) - await basketHandler.connect(owner).refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - - // Provide approvals - await cDaiVault - .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) - - // Advance time significantly - Recharge throttle - await advanceTime(100000) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - - // Get current liquidity - fullLiquidityAmt = await dai.balanceOf(cDai.address) - - // Compound Comptroller - comptroller = await ethers.getContractAt( - 'ComptrollerMock', - networkConfig[chainId].COMPTROLLER || '' - ) - - // Deposit ETH to be able to borrow - const cEtherAbi = [ - 'function mint(uint256 mintAmount) external payable returns (uint256)', - 'function balanceOf(address owner) external view returns (uint256 balance)', - ] - const cEth = await ethers.getContractAt(cEtherAbi, networkConfig[chainId].tokens.cETH || '') - await comptroller.connect(addr1).enterMarkets([cEth.address]) - const amountToDeposit = fp('500000') - await weth.connect(addr1).withdraw(amountToDeposit) - await cEth.connect(addr1).mint(amountToDeposit, { value: amountToDeposit }) - }) - - it('Should return 100% when full liquidity available', async function () { - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) - - // COMPOUND V2 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V2, - cDaiVault.address - ) - ).to.equal(fp('1')) - - // Confirm all can be redeemed - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await cDaiVault.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await cDaiVault.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await cDaiVault.connect(addr2).withdraw(bmBalanceAmt, addr2.address) - expect(await cDai.balanceOf(addr2.address)).to.equal(bmBalanceAmt) - - await expect(cDai.connect(addr2).redeem(bmBalanceAmt)).to.not.be.reverted - expect(await dai.balanceOf(addr2.address)).to.be.gt(bn(0)) - expect(await cDai.balanceOf(addr2.address)).to.equal(bn(0)) - }) - - it('Should return backing redeemable percent correctly', async function () { - // COMPOUND V2 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V2, - cDaiVault.address - ) - ).to.equal(fp('1')) - - // Leave only 80% of backing to be able to be redeemed - const borrowAmount = fullLiquidityAmt.sub(issueAmount.mul(80).div(100)) - await cDai.connect(addr1).borrow(borrowAmount) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V2, - cDaiVault.address - ) - ).to.be.closeTo(fp('0.80'), fp('0.01')) - - // Borrow half of the remaining liquidity - const remainingLiquidity = fullLiquidityAmt.sub(borrowAmount) - await cDai.connect(addr1).borrow(bn(remainingLiquidity.div(2))) - - // Now only 40% of backing can be redeemed - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V2, - cDaiVault.address - ) - ).to.be.closeTo(fp('0.40'), fp('0.01')) - - // Confirm we cannot redeem full balance - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await cDaiVault.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await cDaiVault.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await cDaiVault.connect(addr2).withdraw(bmBalanceAmt, addr2.address) - await expect(cDai.connect(addr2).redeem(bmBalanceAmt)).to.be.reverted - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - - // We can redeem iff we reduce to 30% - await expect(cDai.connect(addr2).redeem(bmBalanceAmt.mul(30).div(100))).to.not.be.reverted - expect(await dai.balanceOf(addr2.address)).to.be.gt(0) - }) - - it('Should handle no liquidity', async function () { - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V2, - cDaiVault.address - ) - ).to.equal(fp('1')) - - // Borrow full liquidity - await cDai.connect(addr1).borrow(fullLiquidityAmt) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V2, - cDaiVault.address - ) - ).to.be.closeTo(fp('0'), fp('0.01')) - - // Confirm we cannot redeem anything, not even 1% - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await cDaiVault.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await cDaiVault.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await cDaiVault.connect(addr2).withdraw(bmBalanceAmt, addr2.address) - expect(await cDai.balanceOf(addr2.address)).to.equal(bmBalanceAmt) - - await expect(cDai.connect(addr2).redeem((await cDai.balanceOf(addr2.address)).div(100))).to - .be.reverted - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - }) - }) - - describe('Compound V3', () => { - const issueAmount: BigNumber = bn('1000000e18') - let wcusdcV3: CusdcV3Wrapper - - beforeEach(async () => { - const CUsdcV3WrapperFactory = await hre.ethers.getContractFactory('CusdcV3Wrapper') - - wcusdcV3 = ( - await CUsdcV3WrapperFactory.deploy( - cusdcV3.address, - networkConfig[chainId].COMET_REWARDS || '', - networkConfig[chainId].tokens.COMP || '' - ) - ) - await wcusdcV3.deployed() - - /******** Deploy Compound V3 USDC collateral plugin **************************/ - const CollateralFactory = await ethers.getContractFactory('CTokenV3Collateral') - - const usdcOracleTimeout = '86400' // 24 hr - const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% - - const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') - const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const collateral = await CollateralFactory.connect(owner).deploy( - { - priceTimeout: bn('604800'), - chainlinkFeed: chainlinkFeed.address, - oracleError: usdcOracleError.toString(), - erc20: wcusdcV3.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: usdcOracleTimeout, // 24h hr, - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.01').add(usdcOracleError).toString(), - delayUntilDefault: bn('86400').toString(), // 24h - }, - fp('1e-6'), - bn('10000e6').toString() // $10k - ) - - // Register and update collateral - await collateral.deployed() - await (await collateral.refresh()).wait() - await pushOracleForward(chainlinkFeed.address) - await assetRegistry.connect(owner).register(collateral.address) - - // Wrap cUSDCV3 - await cusdcV3.connect(addr1).allow(wcusdcV3.address, true) - await wcusdcV3.connect(addr1).deposit(toBNDecimals(initialBal, 6)) - - // Get current liquidity - fullLiquidityAmt = await usdc.balanceOf(cusdcV3.address) - - // Setup basket - await pushOracleForward(chainlinkFeed.address) - await basketHandler.connect(owner).setPrimeBasket([wcusdcV3.address], [fp('1')]) - await basketHandler.connect(owner).refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - - // Provide approvals - await wcusdcV3.connect(addr1).approve(rToken.address, MAX_UINT256) - - // Advance time significantly - Recharge throttle - await advanceTime(100000) - await pushOracleForward(chainlinkFeed.address) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - - // Provide liquidity to be able to borrow - const amountToDeposit = fp('500000') - await weth.connect(addr1).approve(cusdcV3.address, amountToDeposit) - await cusdcV3.connect(addr1).supply(weth.address, amountToDeposit.div(2)) - }) - - it('Should return 100% when full liquidity available', async function () { - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) - - // Compound V3 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V3, - wcusdcV3.address - ) - ).to.equal(fp('1')) - - // Confirm all can be redeemed - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await wcusdcV3.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await wcusdcV3.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await wcusdcV3.connect(addr2).withdraw(MAX_UINT256) - - await expect(cusdcV3.connect(addr2).withdraw(usdc.address, MAX_UINT256)).to.not.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.be.gt(bn(0)) - expect(await cusdcV3.balanceOf(addr2.address)).to.equal(bn(0)) - }) - - it('Should return backing redeemable percent correctly', async function () { - // AAVE V3 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V3, - wcusdcV3.address - ) - ).to.equal(fp('1')) - - // Leave only 80% of backing to be able to be redeemed - const borrowAmount = fullLiquidityAmt.sub(toBNDecimals(issueAmount, 6).mul(80).div(100)) - await cusdcV3.connect(addr1).withdraw(usdc.address, borrowAmount) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V3, - wcusdcV3.address - ) - ).to.be.closeTo(fp('0.80'), fp('0.01')) - - // Borrow half of the remaining liquidity - const remainingLiquidity = fullLiquidityAmt.sub(borrowAmount) - await cusdcV3.connect(addr1).withdraw(usdc.address, remainingLiquidity.div(2)) - - // Only 40% available to be redeemed - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V3, - wcusdcV3.address - ) - ).to.be.closeTo(fp('0.40'), fp('0.01')) - - // Confirm we cannot redeem full balance - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await wcusdcV3.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await wcusdcV3.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await wcusdcV3.connect(addr2).withdraw(MAX_UINT256) - - await expect(cusdcV3.connect(addr2).withdraw(usdc.address, MAX_UINT256)).to.be.reverted - expect(await dai.balanceOf(addr2.address)).to.equal(bn(0)) - - // We can redeem if we reduce to 30% - await expect( - cusdcV3 - .connect(addr2) - .withdraw(usdc.address, (await cusdcV3.balanceOf(addr2.address)).mul(30).div(100)) - ).to.not.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.be.gt(0) - }) - - it('Should handle no liquidity', async function () { - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V3, - wcusdcV3.address - ) - ).to.equal(fp('1')) - - // Borrow full liquidity - await cusdcV3.connect(addr1).withdraw(usdc.address, fullLiquidityAmt) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.COMPOUND_V3, - wcusdcV3.address - ) - ).to.be.closeTo(fp('0'), fp('0.01')) - - // Confirm we cannot redeem anything, not even 1% - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await wcusdcV3.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await wcusdcV3.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await wcusdcV3.connect(addr2).withdraw(MAX_UINT256) - - await expect( - cusdcV3 - .connect(addr2) - .withdraw(usdc.address, (await cusdcV3.balanceOf(addr2.address)).div(100)) - ).to.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - }) - }) - - describe('Stargate', () => { - const issueAmount: BigNumber = bn('1000000e18') - let wstgUsdc: StargateRewardableWrapper - - beforeEach(async () => { - const SthWrapperFactory = await hre.ethers.getContractFactory('StargateRewardableWrapper') - - wstgUsdc = await SthWrapperFactory.deploy( - 'Wrapped Stargate USDC', - 'wsgUSDC', - networkConfig[chainId].tokens.STG!, - networkConfig[chainId].STARGATE_STAKING_CONTRACT!, - networkConfig[chainId].tokens.sUSDC! - ) - await wstgUsdc.deployed() - - /******** Deploy Stargate USDC collateral plugin **************************/ - const usdcOracleTimeout = '86400' // 24 hr - const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% - - const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') - const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const CollateralFactory = await hre.ethers.getContractFactory('StargatePoolFiatCollateral') - const collateral = await CollateralFactory.connect( - owner - ).deploy( - { - priceTimeout: bn('604800'), - chainlinkFeed: chainlinkFeed.address, - oracleError: usdcOracleError, - erc20: wstgUsdc.address, - maxTradeVolume: fp('1e6'), - oracleTimeout: usdcOracleTimeout, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.01').add(usdcOracleError), - delayUntilDefault: bn('86400'), - }, - fp('1e-6') - ) - - // Register and update collateral - await collateral.deployed() - await (await collateral.refresh()).wait() - await pushOracleForward(chainlinkFeed.address) - await assetRegistry.connect(owner).register(collateral.address) - - // Wrap sUsdc - await sUsdc.connect(addr1).approve(wstgUsdc.address, toBNDecimals(initialBal, 6)) - await wstgUsdc.connect(addr1).deposit(toBNDecimals(initialBal, 6), addr1.address) - - // Get current liquidity - fullLiquidityAmt = await sUsdc.totalLiquidity() - - // Setup basket - await pushOracleForward(chainlinkFeed.address) - await basketHandler.connect(owner).setPrimeBasket([wstgUsdc.address], [fp('1')]) - await basketHandler.connect(owner).refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - - // Provide approvals - await wstgUsdc.connect(addr1).approve(rToken.address, issueAmount) - - // Advance time significantly - Recharge throttle - await advanceTime(100000) - await pushOracleForward(chainlinkFeed.address) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - }) - - it('Should return 100%, full liquidity available at all times', async function () { - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) - - // AAVE V3 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.STARGATE, - wstgUsdc.address - ) - ).to.equal(fp('1')) - }) - }) - - describe('Flux', () => { - const issueAmount: BigNumber = bn('1000000e18') - - beforeEach(async () => { - /******** Deploy Flux USDC collateral plugin **************************/ - const CollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') - - 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 MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') - const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const collateral = await CollateralFactory.connect(owner).deploy( - { - priceTimeout: bn('604800'), - chainlinkFeed: chainlinkFeed.address, - oracleError: usdcOracleError.toString(), - erc20: fUsdc.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: usdcOracleTimeout, // 24h hr, - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.01').add(usdcOracleError).toString(), - delayUntilDefault: bn('86400').toString(), // 24h - }, - fp('1e-6') - ) - - // Register and update collateral - await collateral.deployed() - await (await collateral.refresh()).wait() - await pushOracleForward(chainlinkFeed.address) - await assetRegistry.connect(owner).register(collateral.address) - - // Get current liquidity - fullLiquidityAmt = await usdc.balanceOf(fUsdc.address) - - // Setup basket - await pushOracleForward(chainlinkFeed.address) - await basketHandler.connect(owner).setPrimeBasket([fUsdc.address], [fp('1')]) - await basketHandler.connect(owner).refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - - // Provide approvals - await fUsdc.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) - - // Advance time significantly - Recharge throttle - await advanceTime(100000) - await pushOracleForward(chainlinkFeed.address) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - }) - - it('Should return 100% when full liquidity available', async function () { - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) - - // FLUX - All redeemable - expect( - await facadeMonitor.backingReedemable(rToken.address, CollPluginType.FLUX, fUsdc.address) - ).to.equal(fp('1')) - - // Confirm all can be redeemed - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await fUsdc.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await fUsdc.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - await expect(fUsdc.connect(addr2).redeem(bmBalanceAmt)).to.not.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.be.gt(bn(0)) - expect(await fUsdc.balanceOf(addr2.address)).to.equal(bn(0)) - }) - }) - - describe('MORPHO - AAVE V2', () => { - const issueAmount: BigNumber = bn('1000000e18') - let lendingPool: ILendingPool - let maUSDC: MorphoAaveV2TokenisedDeposit - let aaveV2DataProvider: Contract - - beforeEach(async () => { - /******** Deploy Morpho AAVE V2 USDC collateral plugin **************************/ - const MorphoTokenisedDepositFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDeposit' - ) - maUSDC = await MorphoTokenisedDepositFactory.deploy({ - morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, - rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, - underlyingERC20: networkConfig[chainId].tokens.USDC!, - poolToken: networkConfig[chainId].tokens.aUSDC!, - rewardToken: networkConfig[chainId].tokens.MORPHO!, - }) - - const CollateralFactory = await hre.ethers.getContractFactory('MorphoFiatCollateral') - - 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 baseStableConfig = { - priceTimeout: bn('604800').toString(), - oracleError: usdcOracleError.toString(), - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: usdcOracleTimeout, // 24h - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: usdcOracleError.add(fp('0.01')), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - } - const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') - const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const collateral = await CollateralFactory.connect(owner).deploy( - { - ...baseStableConfig, - chainlinkFeed: chainlinkFeed.address, - erc20: maUSDC.address, - }, - fp('1e-6') - ) - - // Register and update collateral - await collateral.deployed() - await (await collateral.refresh()).wait() - await pushOracleForward(chainlinkFeed.address) - await assetRegistry.connect(owner).register(collateral.address) - - const aaveV2DataProviderAbi = [ - 'function getReserveData(address asset) external view returns (uint256 availableLiquidity,uint256 totalStableDebt,uint256 totalVariableDebt,uint256 liquidityRate,uint256 variableBorrowRate,uint256 stableBorrowRate,uint256 averageStableBorrowRate,uint256 liquidityIndex,uint256 variableBorrowIndex,uint40 lastUpdateTimestamp)', - ] - aaveV2DataProvider = await ethers.getContractAt( - aaveV2DataProviderAbi, - networkConfig[chainId].AAVE_DATA_PROVIDER || '' - ) - - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.MORPHO_AAVE_V2, - maUSDC.address - ) - - // Wrap maUSDC - await usdc.connect(addr1).approve(maUSDC.address, 0) - await usdc.connect(addr1).approve(maUSDC.address, MAX_UINT256) - await maUSDC.connect(addr1).mint(toBNDecimals(initialBal, 15), addr1.address) - - // Setup basket - await pushOracleForward(chainlinkFeed.address) - await basketHandler.connect(owner).setPrimeBasket([maUSDC.address], [fp('1')]) - await basketHandler.connect(owner).refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - - // Provide approvals - await maUSDC.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 15)) - - // Advance time significantly - Recharge throttle - await advanceTime(100000) - await pushOracleForward(chainlinkFeed.address) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - - lendingPool = ( - await ethers.getContractAt('ILendingPool', networkConfig[chainId].AAVE_LENDING_POOL || '') - ) - - // Provide liquidity in AAVE V2 to be able to borrow - const amountToDeposit = fp('500000') - await weth.connect(addr1).approve(lendingPool.address, amountToDeposit) - await lendingPool.connect(addr1).deposit(weth.address, amountToDeposit, addr1.address, 0) - }) - - it('Should return 100% when full liquidity available', async function () { - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) - - // MORPHO AAVE V2 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.MORPHO_AAVE_V2, - maUSDC.address - ) - ).to.equal(fp('1')) - - // Confirm all can be redeemed - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await maUSDC.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await maUSDC.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - const maxWithdraw = await maUSDC.maxWithdraw(addr2.address) - await expect(maUSDC.connect(addr2).withdraw(maxWithdraw, addr2.address, addr2.address)).to - .not.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.be.gt(bn(0)) - }) - - it('Should return backing redeemable percent correctly', async function () { - // MORPHO AAVE V2 - All redeemable - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.MORPHO_AAVE_V2, - maUSDC.address - ) - ).to.equal(fp('1')) - - // Get current liquidity from Aave V2 (Morpho relies on this) - ;[fullLiquidityAmt, , , , , , , , ,] = await aaveV2DataProvider - .connect(addr1) - .getReserveData(usdc.address) - - // Leave only 80% of backing available to be redeemed - const borrowAmount = fullLiquidityAmt.sub(toBNDecimals(issueAmount, 6).mul(80).div(100)) - await lendingPool.connect(addr1).borrow(usdc.address, borrowAmount, 2, 0, addr1.address) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.MORPHO_AAVE_V2, - maUSDC.address - ) - ).to.be.closeTo(fp('0.80'), fp('0.01')) - - // Borrow half of the remaining liquidity - const remainingLiquidity = fullLiquidityAmt.sub(borrowAmount) - await lendingPool - .connect(addr1) - .borrow(usdc.address, remainingLiquidity.div(2), 2, 0, addr1.address) - - // Now only 40% is available to be redeemed - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.MORPHO_AAVE_V2, - maUSDC.address - ) - ).to.be.closeTo(fp('0.40'), fp('0.01')) - - // Confirm we cannot redeem full balance - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await maUSDC.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await maUSDC.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - const maxWithdraw = await maUSDC.maxWithdraw(addr2.address) - await expect(maUSDC.connect(addr2).withdraw(maxWithdraw, addr2.address, addr2.address)).to - .be.reverted - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - - // But we can redeem if we reduce the amount to 30% - await expect( - maUSDC.connect(addr2).withdraw(maxWithdraw.mul(30).div(100), addr2.address, addr2.address) - ).to.not.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.be.gt(0) - }) - - it('Should handle no liquidity', async function () { - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.MORPHO_AAVE_V2, - maUSDC.address - ) - ).to.equal(fp('1')) - - // Get current liquidity from Aave V2 (Morpho relies on this) - ;[fullLiquidityAmt, , , , , , , , ,] = await aaveV2DataProvider - .connect(addr1) - .getReserveData(usdc.address) - - // Borrow full liquidity - await lendingPool.connect(addr1).borrow(usdc.address, fullLiquidityAmt, 2, 0, addr1.address) - - expect( - await facadeMonitor.backingReedemable( - rToken.address, - CollPluginType.MORPHO_AAVE_V2, - maUSDC.address - ) - ).to.be.closeTo(fp('0'), fp('0.01')) - - // Confirm we cannot redeem anything, not even 1% - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - const bmBalanceAmt = await maUSDC.balanceOf(backingManager.address) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await maUSDC.connect(bmSigner).transfer(addr2.address, bmBalanceAmt) - }) - const maxWithdraw = await maUSDC.maxWithdraw(addr2.address) - await expect( - maUSDC.connect(addr2).withdraw(maxWithdraw.div(100), addr2.address, addr2.address) - ).to.be.reverted - expect(await usdc.balanceOf(addr2.address)).to.equal(bn(0)) - }) - }) - }) -}) diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 5d801071a7..0a534ae9cc 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -7,14 +7,11 @@ import { advanceBlocks, advanceTime, getLatestBlockTimestamp, - getLatestBlockNumber, setNextBlockTimestamp, } from '../utils/time' -import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192, TradeKind } from '../../common/constants' +import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192 } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { - expectDecayedPrice, - expectExactPrice, expectPrice, expectRTokenPrice, expectUnpriced, @@ -29,15 +26,12 @@ import { CTokenWrapperMock, ERC20Mock, FiatCollateral, - GnosisMock, IAssetRegistry, InvalidFiatCollateral, InvalidMockV3Aggregator, RTokenAsset, StaticATokenMock, TestIBackingManager, - TestIBasketHandler, - TestIFurnace, TestIRToken, USDCMock, UnpricedAssetMock, @@ -48,12 +42,10 @@ import { IMPLEMENTATION, Implementation, ORACLE_TIMEOUT, - ORACLE_TIMEOUT_PRE_BUFFER, ORACLE_ERROR, PRICE_TIMEOUT, VERSION, } from '../fixtures' -import { getTrade } from '../utils/trades' import { useEnv } from '#/utils/env' import snapshotGasCost from '../utils/snapshotGasCost' @@ -94,16 +86,11 @@ describe('Assets contracts #fast', () => { let wallet: Wallet let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: TestIBasketHandler - let furnace: TestIFurnace // Factory let AssetFactory: ContractFactory let RTokenAssetFactory: ContractFactory - // Gnosis - let gnosis: GnosisMock - const amt = fp('1e4') before('create fixture loader', async () => { @@ -122,10 +109,7 @@ describe('Assets contracts #fast', () => { basket, assetRegistry, backingManager, - basketHandler, config, - gnosis, - furnace, rToken, rTokenAsset, } = await loadFixture(defaultFixture)) @@ -276,51 +260,34 @@ 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')) - - // 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 + // Should be unpriced 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 () => { @@ -350,6 +317,12 @@ describe('Assets contracts #fast', () => { config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) expect(await rTokenAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + + // Should have lot price, equal to price when feed works OK + const [lowPrice, highPrice] = await rTokenAsset.price() + const [lotLow, lotHigh] = await rTokenAsset.lotPrice() + expect(lotLow).to.equal(lowPrice) + expect(lotHigh).to.equal(highPrice) }) it('Should calculate trade min correctly', async () => { @@ -376,62 +349,38 @@ describe('Assets contracts #fast', () => { expect(await rTokenAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) }) - it('Should remain at saved price if oracle is stale', async () => { - await advanceTime(ORACLE_TIMEOUT.sub(12).toString()) + it('Should be unpriced if price is stale', async () => { + await advanceTime(ORACLE_TIMEOUT.toString()) - // 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) + // Check unpriced + await expectUnpriced(rsrAsset.address) + await expectUnpriced(compAsset.address) + await expectUnpriced(aaveAsset.address) }) - it('Should remain at saved price in case of invalid timestamp', async () => { + it('Should be unpriced in case of invalid timestamp', async () => { await setInvalidOracleTimestamp(rsrAsset.address) await setInvalidOracleTimestamp(compAsset.address) await setInvalidOracleTimestamp(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) + // Check unpriced + await expectUnpriced(rsrAsset.address) + await expectUnpriced(compAsset.address) + await expectUnpriced(aaveAsset.address) }) - it('Should remain at saved price in case of invalid answered round', async () => { + it('Should be unpriced in case of invalid answered round', async () => { await setInvalidOracleAnsweredRound(rsrAsset.address) await setInvalidOracleAnsweredRound(compAsset.address) await setInvalidOracleAnsweredRound(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) + // Check unpriced + await expectUnpriced(rsrAsset.address) + await expectUnpriced(compAsset.address) + await expectUnpriced(aaveAsset.address) }) - it('Should handle reverting edge cases for RTokenAsset', async () => { + it('Should handle reverting edge cases for RToken', async () => { // Swap one of the collaterals for an invalid one const InvalidFiatCollateralFactory = await ethers.getContractFactory('InvalidFiatCollateral') const invalidFiatCollateral: InvalidFiatCollateral = ( @@ -441,7 +390,7 @@ describe('Assets contracts #fast', () => { oracleError: ORACLE_ERROR, erc20: await collateral0.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -466,7 +415,7 @@ describe('Assets contracts #fast', () => { await expect(rTokenAsset.price()).to.be.reverted }) - it('Regression test -- Should handle unpriced collateral for RTokenAsset', async () => { + it('Regression test -- Should handle unpriced collateral for RToken', async () => { // https://github.com/code-423n4/2023-07-reserve-findings/issues/20 // Swap one of the collaterals for an invalid one @@ -478,7 +427,7 @@ describe('Assets contracts #fast', () => { oracleError: ORACLE_ERROR, erc20: await collateral0.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -495,115 +444,6 @@ 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 handle tokens being out on trade for RTokenAsset', async () => { - // Summary: - // - Run a dutch auction that does not fill - // - Run a batch auction that fills for partial volume - // - Run a dutch auction that fills for full volume - - const low0 = fp('0.99') - const low1 = bn('975344098811881188') // after a 50% basket change - const low2 = bn('975343128415841584') // after batch auction at half volume - const low3 = bn('975560049627103964') // after dutch auction at full volume - - // Price should be [$0.99, $1.01] to start - await expectExactPrice(rTokenAsset.address, [low0, fp('1.01')]) - - // After 50% basket change, expected trading should decrease the lower price to ~$0.9753 - // Upper price remains $1.01 because of uncertainty around how trading will go - await basketHandler - .connect(wallet) - .setPrimeBasket([token.address, usdc.address], [fp('0.5'), fp('0.5')]) - await basketHandler.connect(wallet).refreshBasket() - await expectExactPrice(rTokenAsset.address, [low1, fp('1.01')]) - - // After launching a trade token price should not change - // Regression -- I've confirmed the lower price drops to ~$0.7352 when not tracking balances out on trade - await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.emit( - backingManager, - 'TradeStarted' - ) - expect(await backingManager.tradesOpen()).to.equal(1) - await expectExactPrice(rTokenAsset.address, [low1, fp('1.01')]) - - // Settling trade without bidding should not change price - let trade = await ethers.getContractAt( - 'DutchTrade', - await backingManager.trades(aToken.address) - ) - await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber())) - await expect(backingManager.settleTrade(aToken.address)).to.emit( - backingManager, - 'TradeSettled' - ) - expect(await backingManager.tradesOpen()).to.equal(0) - await expectExactPrice(rTokenAsset.address, [low1, fp('1.01')]) - - // Launching the trade a second time, this time Batch Auction, should not change price - await setNextBlockTimestamp((await trade.endTime()) + 13) - await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( - backingManager, - 'TradeStarted' - ) - expect(await backingManager.tradesOpen()).to.equal(1) - await expectExactPrice(rTokenAsset.address, [low1, fp('1.01')]) - - // Bid in Gnosis for half volume at even prices - const t = await getTrade(backingManager, aToken.address) - const sellAmt = (await t.initBal()).div(2) // half volume - await token.connect(wallet).approve(gnosis.address, sellAmt) - await gnosis.placeBid(0, { - bidder: wallet.address, - sellAmount: sellAmt, - buyAmount: sellAmt, - }) - await advanceTime(config.batchAuctionLength.toNumber()) - await expect(backingManager.settleTrade(aToken.address)).not.to.emit( - backingManager, - 'TradeStarted' - ) - expect(await backingManager.tradesOpen()).to.equal(0) - await expectExactPrice(rTokenAsset.address, [low2, fp('1.01')]) - - // Starting a 3rd auction should not change balances - await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.emit( - backingManager, - 'TradeStarted' - ) - expect(await backingManager.tradesOpen()).to.equal(1) - await expectExactPrice(rTokenAsset.address, [low2, fp('1.01')]) - - // Settle 3rd auction for full volume - trade = await ethers.getContractAt('DutchTrade', await backingManager.trades(cToken.address)) - const buyAmt = await trade.bidAmount(await trade.endBlock()) - await usdc.approve(trade.address, buyAmt) - await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) - await expect(trade.bid()).to.emit(backingManager, 'TradeSettled') - expect(await backingManager.tradesOpen()).to.equal(1) // launches another trade! - await expectExactPrice(rTokenAsset.address, [low3, bn('1007427552565834095')]) // high end starts to fall - }) - it('Should be able to refresh saved prices', async () => { // Check initial prices - use RSR as example let currBlockTimestamp: number = await getLatestBlockTimestamp() @@ -654,7 +494,7 @@ describe('Assets contracts #fast', () => { ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -693,35 +533,37 @@ describe('Assets contracts #fast', () => { expect(await unpricedRSRAsset.lastSave()).to.equal(currBlockTimestamp) }) - it('Should not revert on refresh if stale', async () => { + it('Should not revert on refresh if unpriced', async () => { // Check initial prices - use RSR as example - const startBlockTimestamp: number = await getLatestBlockTimestamp() - await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, false) + const currBlockTimestamp: number = await getLatestBlockTimestamp() + await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, true) 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(startBlockTimestamp) + expect(await rsrAsset.lastSave()).to.equal(currBlockTimestamp) // Set invalid oracle await setInvalidOracleTimestamp(rsrAsset.address) - // Check price - uses still previous prices - await rsrAsset.refresh() + // Check unpriced - uses still previous prices + await expectUnpriced(rsrAsset.address) let [lowPrice, highPrice] = await rsrAsset.price() - expect(lowPrice).to.equal(prevLowPrice) - expect(highPrice).to.equal(prevHighPrice) + expect(lowPrice).to.equal(bn(0)) + expect(highPrice).to.equal(MAX_UINT192) expect(await rsrAsset.savedLowPrice()).to.equal(prevLowPrice) expect(await rsrAsset.savedHighPrice()).to.equal(prevHighPrice) - expect(await rsrAsset.lastSave()).to.equal(startBlockTimestamp) + expect(await rsrAsset.lastSave()).to.equal(currBlockTimestamp) - // Check price - no update on prices/timestamp + // Perform refresh await rsrAsset.refresh() + + // Check still unpriced - no update on prices/timestamp + await expectUnpriced(rsrAsset.address) ;[lowPrice, highPrice] = await rsrAsset.price() - expect(lowPrice).to.equal(prevLowPrice) - expect(highPrice).to.equal(prevHighPrice) + expect(lowPrice).to.equal(bn(0)) + expect(highPrice).to.equal(MAX_UINT192) 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 () => { @@ -739,82 +581,79 @@ describe('Assets contracts #fast', () => { ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) // 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 price decay correctly', async () => { + it('Should handle lot price correctly', async () => { await rsrAsset.refresh() - // Check prices - use RSR as example - const startBlockTimestamp: number = await getLatestBlockTimestamp() - await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, false) + // Check lot prices - use RSR as example + const currBlockTimestamp: number = await getLatestBlockTimestamp() + await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, true) 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(startBlockTimestamp) + 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) // 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(prevLowPrice) - expect(highPrice).to.equal(prevHighPrice) + expect(lowPrice).to.equal(bn(0)) + expect(highPrice).to.equal(MAX_UINT192) expect(await rsrAsset.savedLowPrice()).to.equal(prevLowPrice) expect(await rsrAsset.savedHighPrice()).to.equal(prevHighPrice) - expect(await rsrAsset.lastSave()).to.equal(startBlockTimestamp) + expect(await rsrAsset.lastSave()).to.equal(currBlockTimestamp) - // At first price doesn't decrease - const [lowPrice2, highPrice2] = await rsrAsset.price() - expect(lowPrice2).to.eq(lowPrice) - expect(highPrice2).to.eq(highPrice) + // At first lot price doesn't decrease + const [lotLowPrice2, lotHighPrice2] = await rsrAsset.lotPrice() + expect(lotLowPrice2).to.eq(lotLowPrice1) + expect(lotHighPrice2).to.eq(lotHighPrice1) // Advance past oracleTimeout await advanceTime(await rsrAsset.oracleTimeout()) - // Now price widens - const [lowPrice3, highPrice3] = await rsrAsset.price() - expect(lowPrice3).to.be.lt(lowPrice2) - expect(highPrice3).to.be.gt(highPrice2) + // Now lot price decreases + const [lotLowPrice3, lotHighPrice3] = await rsrAsset.lotPrice() + expect(lotLowPrice3).to.be.lt(lotLowPrice2) + expect(lotHighPrice3).to.be.lt(lotHighPrice2) - // Advance block, price keeps widening + // Advance block, lot price keeps decreasing await advanceBlocks(1) - const [lowPrice4, highPrice4] = await rsrAsset.price() - expect(lowPrice4).to.be.lt(lowPrice3) - expect(highPrice4).to.be.gt(highPrice3) + const [lotLowPrice4, lotHighPrice4] = await rsrAsset.lotPrice() + expect(lotLowPrice4).to.be.lt(lotLowPrice3) + expect(lotHighPrice4).to.be.lt(lotHighPrice3) - // Advance blocks beyond PRICE_TIMEOUT; price should be [O, FIX_MAX] + // Advance blocks beyond PRICE_TIMEOUT await advanceTime(PRICE_TIMEOUT.toNumber()) // Lot price returns 0 once time elapses - 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) - }) - - it('lotPrice (deprecated) is equal to price()', async () => { - for (const asset of [rsrAsset, compAsset, aaveAsset, rTokenAsset]) { - const lotPrice = await asset.lotPrice() - const price = await asset.price() - expect(price.length).to.equal(2) - expect(lotPrice.length).to.equal(price.length) - expect(lotPrice[0]).to.equal(price[0]) - expect(lotPrice[1]).to.equal(price[1]) - } + 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)) }) }) @@ -885,9 +724,9 @@ describe('Assets contracts #fast', () => { it('refresh() after full price timeout', async () => { await advanceTime((await rsrAsset.priceTimeout()) + (await rsrAsset.oracleTimeout())) - const p = await rsrAsset.price() - expect(p[0]).to.equal(0) - expect(p[1]).to.equal(MAX_UINT192) + const lotP = await rsrAsset.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) }) }) }) diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index 21ca93859c..ff0441b43c 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -39,8 +39,6 @@ import { } from '../utils/time' import snapshotGasCost from '../utils/snapshotGasCost' import { - expectDecayedPrice, - expectExactPrice, expectPrice, expectRTokenPrice, expectUnpriced, @@ -52,7 +50,6 @@ import { Collateral, defaultFixture, ORACLE_TIMEOUT, - ORACLE_TIMEOUT_PRE_BUFFER, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING, @@ -255,7 +252,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.constants.HashZero, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -263,44 +260,6 @@ 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({ @@ -309,7 +268,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -325,7 +284,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -343,7 +302,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -361,7 +320,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: MAX_DELAY_UNTIL_DEFAULT + 1, @@ -377,7 +336,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: MAX_DELAY_UNTIL_DEFAULT + 1, @@ -395,7 +354,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: MAX_DELAY_UNTIL_DEFAULT + 1, @@ -414,7 +373,7 @@ describe('Collateral contracts', () => { oracleError: bn('0'), erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -430,7 +389,7 @@ describe('Collateral contracts', () => { oracleError: bn('0'), erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -448,7 +407,7 @@ describe('Collateral contracts', () => { oracleError: bn('0'), erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -467,7 +426,7 @@ describe('Collateral contracts', () => { oracleError: fp('1'), erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -483,7 +442,7 @@ describe('Collateral contracts', () => { oracleError: fp('1'), erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -501,7 +460,7 @@ describe('Collateral contracts', () => { oracleError: fp('1'), erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -522,7 +481,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -540,7 +499,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -623,7 +582,7 @@ describe('Collateral contracts', () => { ) }) - it('Should handle prices correctly when price is zero', async () => { + it('Should become unpriced if price is zero', async () => { const compInitPrice = await tokenCollateral.price() const aaveInitPrice = await aTokenCollateral.price() const rsrInitPrice = await cTokenCollateral.price() @@ -631,25 +590,22 @@ describe('Collateral contracts', () => { // Update values in Oracles to 0 await setOraclePrice(tokenCollateral.address, bn('0')) + // Should be unpriced + await expectUnpriced(cTokenCollateral.address) + await expectUnpriced(tokenCollateral.address) + await expectUnpriced(aTokenCollateral.address) + // Fallback prices should be initial prices - let [lotLow, lotHigh] = await tokenCollateral.price() + let [lotLow, lotHigh] = await tokenCollateral.lotPrice() expect(lotLow).to.eq(compInitPrice[0]) expect(lotHigh).to.eq(compInitPrice[1]) - ;[lotLow, lotHigh] = await cTokenCollateral.price() + ;[lotLow, lotHigh] = await cTokenCollateral.lotPrice() expect(lotLow).to.eq(rsrInitPrice[0]) expect(lotHigh).to.eq(rsrInitPrice[1]) - ;[lotLow, lotHigh] = await aTokenCollateral.price() + ;[lotLow, lotHigh] = await aTokenCollateral.lotPrice() 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() @@ -660,56 +616,38 @@ describe('Collateral contracts', () => { expect(await cTokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) - it('Should remain at saved price in case of invalid timestamp', async () => { + it('Should be unpriced in case of invalid timestamp', async () => { await setInvalidOracleTimestamp(tokenCollateral.address) - await setInvalidOracleTimestamp(usdcCollateral.address) - // lastSave should not be block timestamp after refresh + // Check price of token + await expectUnpriced(tokenCollateral.address) + await expectUnpriced(aTokenCollateral.address) + await expectUnpriced(cTokenCollateral.address) + + // When refreshed, sets status to Unpriced 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 remain at saved price in case of invalid answered round', async () => { + it('Should be unpriced in case of invalid answered round', async () => { await setInvalidOracleAnsweredRound(tokenCollateral.address) - await setInvalidOracleAnsweredRound(usdcCollateral.address) - // lastSave should not be block timestamp after refresh + // Check price of token + await expectUnpriced(tokenCollateral.address) + await expectUnpriced(aTokenCollateral.address) + await expectUnpriced(cTokenCollateral.address) + + // When refreshed, sets status to Unpriced 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) }) @@ -776,7 +714,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -818,17 +756,6 @@ describe('Collateral contracts', () => { expect(await unpricedAppFiatCollateral.savedHighPrice()).to.equal(highPrice) expect(await unpricedAppFiatCollateral.lastSave()).to.equal(currBlockTimestamp) }) - - it('lotPrice (deprecated) is equal to price()', async () => { - for (const coll of [tokenCollateral, usdcCollateral, aTokenCollateral, cTokenCollateral]) { - const lotPrice = await coll.lotPrice() - const price = await coll.price() - expect(price.length).to.equal(2) - expect(lotPrice.length).to.equal(price.length) - expect(lotPrice[0]).to.equal(price[0]) - expect(lotPrice[1]).to.equal(price[1]) - } - }) }) describe('Status', () => { @@ -981,24 +908,14 @@ describe('Collateral contracts', () => { } }) - it('Should remain at saved price if oracle is stale', async () => { - await advanceTime(ORACLE_TIMEOUT.sub(12).toString()) - - // 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()) + it('Unpriced if price is stale', async () => { + await advanceTime(ORACLE_TIMEOUT.toString()) - // 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) + // Check unpriced + await expectUnpriced(tokenCollateral.address) + await expectUnpriced(usdcCollateral.address) + await expectUnpriced(cTokenCollateral.address) + await expectUnpriced(aTokenCollateral.address) }) it('Enters IFFY state when price becomes stale', async () => { @@ -1192,13 +1109,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) await nonFiatCollateral.refresh() @@ -1215,13 +1132,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), }, targetUnitOracle.address, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ).to.be.revertedWith('delayUntilDefault zero') }) @@ -1235,13 +1152,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, ZERO_ADDRESS, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ).to.be.revertedWith('missing targetUnit feed') }) @@ -1255,13 +1172,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ).to.be.revertedWith('missing chainlink feed') }) @@ -1275,7 +1192,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1286,26 +1203,6 @@ 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) @@ -1334,8 +1231,6 @@ 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) @@ -1345,37 +1240,26 @@ describe('Collateral contracts', () => { // Check new prices await expectPrice(nonFiatCollateral.address, fp('22000'), ORACLE_ERROR, true) - // Cached but IFFY if price is zero + // Unpriced if price is zero - Update Oracles and check prices 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() - await expectExactPrice(nonFiatCollateral.address, initialPrice) - - // Check the other oracle's impact - await referenceUnitOracle.updateAnswer(bn('0')) - await expectExactPrice(nonFiatCollateral.address, initialPrice) - - // Advance past oracle timeout - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) - await referenceUnitOracle.updateAnswer(bn('0')) - await expectDecayedPrice(nonFiatCollateral.address) + expect(await nonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) - // Advance past price timeout - await advanceTime(PRICE_TIMEOUT.toString()) + // Check the other oracle await referenceUnitOracle.updateAnswer(bn('0')) await expectUnpriced(nonFiatCollateral.address) + + // When refreshed, sets status to IFFY + await nonFiatCollateral.refresh() + expect(await nonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { @@ -1391,13 +1275,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -1419,13 +1303,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, invalidChainlinkFeed.address, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) // Reverting with no reason @@ -1480,13 +1364,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, REVENUE_HIDING ) await cTokenNonFiatCollateral.refresh() @@ -1504,13 +1388,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), }, targetUnitOracle.address, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, REVENUE_HIDING ) ).to.be.revertedWith('delayUntilDefault zero') @@ -1525,13 +1409,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, fp('1') ) ).to.be.revertedWith('revenueHiding out of range') @@ -1546,13 +1430,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, REVENUE_HIDING ) ).to.be.revertedWith('missing chainlink feed') @@ -1567,13 +1451,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, ZERO_ADDRESS, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, REVENUE_HIDING ) ).to.be.revertedWith('missing targetUnit feed') @@ -1588,7 +1472,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1600,27 +1484,6 @@ 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) @@ -1659,128 +1522,47 @@ 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) - // Should be SOUND - await cTokenNonFiatCollateral.refresh() - expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) - - const initialPrice = await cTokenNonFiatCollateral.price() - - // Cached but IFFY when price becomes zero + // Unpriced if price is zero - Update Oracles and check prices await targetUnitOracle.updateAnswer(bn('0')) - await cTokenNonFiatCollateral.refresh() - expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) - await expectExactPrice(cTokenNonFiatCollateral.address, initialPrice) + await expectUnpriced(cTokenNonFiatCollateral.address) - // 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) + // When refreshed, sets status to IFFY await cTokenNonFiatCollateral.refresh() - await expectDecayedPrice(cTokenNonFiatCollateral.address) + expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) - // Restore price + // Restore await targetUnitOracle.updateAnswer(bn('22000e8')) - await referenceUnitOracle.updateAnswer(bn('1e8')) await cTokenNonFiatCollateral.refresh() - 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) + expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) - // Advance past price timeout - await advanceTime(PRICE_TIMEOUT.toString()) + // Revert if price is zero - Update the other Oracle await referenceUnitOracle.updateAnswer(bn('0')) await expectUnpriced(cTokenNonFiatCollateral.address) - }) - - 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) + // 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 () => { @@ -1828,7 +1610,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1857,7 +1639,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1899,7 +1681,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: selfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: 0, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1939,7 +1721,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: selfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn(100), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1957,17 +1739,9 @@ describe('Collateral contracts', () => { // Check new prices await expectPrice(selfReferentialCollateral.address, fp('1.1'), ORACLE_ERROR, true) - await selfReferentialCollateral.refresh() - const initialPrice = await selfReferentialCollateral.price() - - // Cached price if oracle price is zero - await setOraclePrice(selfReferentialCollateral.address, bn(0)) - await expectExactPrice(selfReferentialCollateral.address, initialPrice) - - // Decay starts after oracle timeout - await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + // Unpriced if price is zero - Update Oracles and check prices await setOraclePrice(selfReferentialCollateral.address, bn(0)) - await expectDecayedPrice(selfReferentialCollateral.address) + await expectUnpriced(selfReferentialCollateral.address) // When refreshed, sets status to IFFY await selfReferentialCollateral.refresh() @@ -1984,12 +1758,6 @@ 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 () => { @@ -2004,7 +1772,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: selfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: 0, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2054,7 +1822,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: 0, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2078,7 +1846,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2098,7 +1866,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2118,7 +1886,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn(200), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2182,90 +1950,13 @@ describe('Collateral contracts', () => { // Check new prices await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.044'), ORACLE_ERROR, true) - await cTokenSelfReferentialCollateral.refresh() - const initialPrice = await cTokenSelfReferentialCollateral.price() - await setOraclePrice(cTokenSelfReferentialCollateral.address, bn(0)) - await expectExactPrice(cTokenSelfReferentialCollateral.address, initialPrice) - - // 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()) + // Unpriced if price is zero - Update Oracles and check prices await setOraclePrice(cTokenSelfReferentialCollateral.address, bn(0)) await expectUnpriced(cTokenSelfReferentialCollateral.address) - // When refreshed, sets status to DISABLED + // When refreshed, sets status to IFFY 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) + expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) }) it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { @@ -2313,7 +2004,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: 0, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2363,7 +2054,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2386,7 +2077,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -2406,7 +2097,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2426,7 +2117,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2446,7 +2137,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2457,26 +2148,6 @@ 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) @@ -2522,12 +2193,10 @@ describe('Collateral contracts', () => { // Check new prices await expectPrice(eurFiatCollateral.address, fp('2'), ORACLE_ERROR, true) - await eurFiatCollateral.refresh() - const initialPrice = await eurFiatCollateral.price() - // Decays if price is zero + // Unpriced if price is zero - Update Oracles and check prices await referenceUnitOracle.updateAnswer(bn('0')) - await expectExactPrice(eurFiatCollateral.address, initialPrice) + await expectUnpriced(eurFiatCollateral.address) // When refreshed, sets status to IFFY await eurFiatCollateral.refresh() @@ -2542,18 +2211,6 @@ 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 () => { @@ -2569,7 +2226,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2597,7 +2254,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2672,15 +2329,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()) ) - await expectUnpriced(tokenCollateral.address) - await snapshotGasCost(tokenCollateral.refresh()) + const lotP = await tokenCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) }) }) }) diff --git a/test/plugins/RewardableERC20.test.ts b/test/plugins/RewardableERC20.test.ts index 2b10d1847b..abc94deb66 100644 --- a/test/plugins/RewardableERC20.test.ts +++ b/test/plugins/RewardableERC20.test.ts @@ -18,16 +18,12 @@ import snapshotGasCost from '../utils/snapshotGasCost' import { formatUnits, parseUnits } from 'ethers/lib/utils' import { MAX_UINT256 } from '#/common/constants' -const SHARE_DECIMAL_OFFSET = 9 // decimals buffer for shares and rewards per share -const BN_SHARE_FACTOR = bn(10).pow(SHARE_DECIMAL_OFFSET) - 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] @@ -80,7 +76,6 @@ for (const wrapperName of wrapperNames) { rewardableVault, rewardableAsset, rewardToken, - rewardableVaultFactory, } } return fixture @@ -123,19 +118,18 @@ 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 let bob: Wallet const initBalance = parseUnits('10000', assetDecimals) - let rewardAmount = parseUnits('200', rewardDecimals) + const rewardAmount = parseUnits('200', rewardDecimals) let oneShare: BigNumber let initShares: BigNumber @@ -147,16 +141,14 @@ for (const wrapperName of wrapperNames) { beforeEach(async () => { // Deploy fixture - ;({ rewardableVault, rewardableAsset, rewardToken, rewardableVaultFactory } = - await loadFixture(fixture)) + ;({ rewardableVault, rewardableAsset, rewardToken } = await loadFixture(fixture)) await rewardableAsset.mint(alice.address, initBalance) await rewardableAsset.connect(alice).approve(rewardableVault.address, initBalance) await rewardableAsset.mint(bob.address, initBalance) await rewardableAsset.connect(bob).approve(rewardableVault.address, initBalance) - shareDecimals = (await rewardableVault.decimals()) + SHARE_DECIMAL_OFFSET - rewardShareDecimals = rewardDecimals + SHARE_DECIMAL_OFFSET + shareDecimals = await rewardableVault.decimals() initShares = toShares(initBalance, assetDecimals, shareDecimals) oneShare = bn('1').mul(bn(10).pow(shareDecimals)) }) @@ -189,9 +181,7 @@ 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', rewardShareDecimals) - ) + expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) }) it('correctly handles reward tracking if supply is burned', async () => { @@ -202,9 +192,7 @@ 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', rewardShareDecimals) - ) + expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) // Setting supply to 0 await withdrawAll(rewardableVault.connect(alice)) @@ -223,9 +211,7 @@ for (const wrapperName of wrapperNames) { // Nothing updates.. as totalSupply as totalSupply is 0 await rewardableVault.sync() - expect(await rewardableVault.rewardsPerShare()).to.equal( - parseUnits('1', rewardShareDecimals) - ) + expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) await rewardableVault .connect(alice) .deposit(parseUnits('10', assetDecimals), alice.address) @@ -237,23 +223,6 @@ 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)) @@ -290,9 +259,7 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.mul(3).div(8).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(alice.address) - ) + expect(initShares.mul(3).div(8)).equal(await rewardableVault.balanceOf(alice.address)) }) it('alice shows correct lastRewardsPerShare', async () => { @@ -300,9 +267,7 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(8).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(bob.address) - ) + expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -311,9 +276,7 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal( - rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) - ) + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) }) }) @@ -340,9 +303,7 @@ 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)).mul(BN_SHARE_FACTOR) - ) + expect(initRewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) expect(initRewardsPerShare).equal( await rewardableVault.lastRewardsPerShare(alice.address) ) @@ -353,7 +314,6 @@ 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)) }) @@ -377,9 +337,7 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal( - rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) - ) + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) }) }) @@ -420,9 +378,7 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal( - rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) - ) + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) }) }) @@ -448,9 +404,7 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(bob.address) - ) + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -459,9 +413,7 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal( - rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) - ) + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) }) }) @@ -481,9 +433,7 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(alice.address) - ) + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) }) it('alice has claimed rewards', async () => { @@ -495,9 +445,7 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(8).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(bob.address) - ) + expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -506,29 +454,10 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal( - rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) - ) + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) }) }) - it('Cannot frontrun claimRewards by inflating your shares', async () => { - await rewardableAsset.connect(bob).approve(rewardableVault.address, MAX_UINT256) - await rewardableAsset.mint(bob.address, initBalance.mul(100)) - await rewardableVault.connect(alice).deposit(initBalance, alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - - // Bob 'flashloans' 100x the current balance of the vault and claims rewards - await rewardableVault.connect(bob).deposit(initBalance.mul(100), bob.address) - await rewardableVault.connect(bob).claimRewards() - - // Alice claimsRewards a bit later - await rewardableVault.connect(alice).claimRewards() - expect(await rewardToken.balanceOf(alice.address)).to.be.gt( - await rewardToken.balanceOf(bob.address) - ) - }) - describe('alice deposit, accrue, bob deposit, accrue, bob claim, alice claim', () => { let rewardsPerShare: BigNumber @@ -551,9 +480,7 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(alice.address) - ) + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) }) it('alice has claimed rewards', async () => { @@ -567,9 +494,7 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(bob.address) - ) + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -586,7 +511,6 @@ 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) }) }) @@ -616,9 +540,7 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(alice.address) - ) + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) }) it('alice shows correct lastRewardsPerShare', async () => { @@ -630,9 +552,7 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(bob.address) - ) + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -645,9 +565,7 @@ 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).mul(BN_SHARE_FACTOR) - ) + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4)).mul(2)) }) }) @@ -658,9 +576,7 @@ 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).div(BN_SHARE_FACTOR)) + await rewardableVault.connect(alice).transfer(bob.address, initShares.div(4)) await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) await rewardableVault.connect(bob).claimRewards() @@ -670,9 +586,7 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(alice.address) - ) + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) }) it('alice shows correct lastRewardsPerShare', async () => { @@ -684,9 +598,7 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(2).div(BN_SHARE_FACTOR)).equal( - await rewardableVault.balanceOf(bob.address) - ) + expect(initShares.div(2)).equal(await rewardableVault.balanceOf(bob.address)) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -704,84 +616,6 @@ for (const wrapperName of wrapperNames) { .mul(oneShare) .div(initShares.div(4)) .add(rewardAmount.mul(oneShare).div(initShares.div(2))) - .mul(BN_SHARE_FACTOR) - ) - }) - }) - - describe('correctly applies fractional reward tracking', () => { - rewardAmount = parseUnits('1.9', rewardDecimals) - - beforeEach(async () => { - // Deploy fixture - ;({ rewardableVault, rewardableAsset } = await loadFixture(fixture)) - - await rewardableAsset.mint(alice.address, initBalance) - await rewardableAsset.connect(alice).approve(rewardableVault.address, MAX_UINT256) - await rewardableAsset.mint(bob.address, initBalance) - await rewardableAsset.connect(bob).approve(rewardableVault.address, MAX_UINT256) - }) - - it('Correctly handles fractional rewards', async () => { - expect(await rewardableVault.rewardsPerShare()).to.equal(0) - - await rewardableVault.connect(alice).deposit(initBalance, alice.address) - - for (let i = 0; i < 10; i++) { - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault.claimRewards() - expect(await rewardableVault.rewardsPerShare()).to.equal( - rewardAmount - .mul(i + 1) - .mul(oneShare) - .div(initShares) - .mul(BN_SHARE_FACTOR) - ) - } - }) - }) - - describe(`correctly rounds rewards`, () => { - // Assets - rewardAmount = parseUnits('1.7', rewardDecimals) - - 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(rewardAmount.mul(2)) - - // Bob only got half of the second rewards - expect(await rewardToken.balanceOf(bob.address)).to.equal(rewardAmount) - - expect(await rewardableVault.rewardsPerShare()).equal( - rewardAmount.mul(2).mul(oneShare).div(initShares).mul(BN_SHARE_FACTOR) ) }) }) @@ -833,67 +667,9 @@ 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( - bn(`1.9e${SHARE_DECIMAL_OFFSET}`).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_DECIMAL_OFFSET}`)) + expect(await rewardableVault.rewardsPerShare()).to.equal(Math.floor(1.9 * (i + 1))) + } }) }) diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index 926d33902f..83c6bf2eb6 100644 --- a/test/plugins/__snapshots__/Collateral.test.ts.snap +++ b/test/plugins/__snapshots__/Collateral.test.ts.snap @@ -1,12 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral contracts Gas Reporting refresh() after full price timeout 1`] = `46228`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `71881`; -exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `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() after hard default 2`] = `75163`; 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 a15ac37a23..89bc877c66 100644 --- a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts @@ -15,7 +15,6 @@ 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, @@ -73,9 +72,6 @@ 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()) @@ -215,7 +211,6 @@ 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 996921a268..62ee74c7f7 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`] = `69288`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69299`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67620`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67631`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 1`] = `72103`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 1`] = `72125`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 2`] = `64421`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 2`] = `64443`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `69288`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `69299`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67620`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67631`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 1`] = `87699`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 1`] = `67290`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 2`] = `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() during SOUND 1`] = `87684`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 1`] = `87706`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 2`] = `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 soft default 1`] = `89708`; +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 2`] = `87966`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 2`] = `87988`; diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 7a4a52862c..ed84d0b200 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -10,13 +10,7 @@ import { PRICE_TIMEOUT, REVENUE_HIDING, } from '../../../fixtures' -import { - DefaultFixture, - Fixture, - getDefaultFixture, - ORACLE_TIMEOUT, - ORACLE_TIMEOUT_PRE_BUFFER, -} from '../fixtures' +import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' import { @@ -28,20 +22,15 @@ import { IRTokenSetup, networkConfig, } from '../../../../common/configuration' -import { - CollateralStatus, - MAX_UINT48, - MAX_UINT192, - ZERO_ADDRESS, -} from '../../../../common/constants' +import { CollateralStatus, MAX_UINT48, ZERO_ADDRESS } from '../../../../common/constants' import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' import { bn, fp } from '../../../../common/numbers' import { whileImpersonating } from '../../../utils/impersonation' import { expectPrice, expectRTokenPrice, - setOraclePrice, expectUnpriced, + setOraclePrice, } from '../../../utils/oracles' import { advanceBlocks, @@ -215,7 +204,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ORACLE_ERROR, stkAave.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -239,7 +228,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: staticAToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -434,7 +423,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 () => { - // Missing erc20 + // stkAAVEtroller await expect( ATokenFiatCollateralFactory.deploy( { @@ -443,7 +432,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: ZERO_ADDRESS, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -451,24 +440,6 @@ 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') }) }) @@ -682,14 +653,10 @@ 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.sub(12).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) - // 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) + // stkAAVEound + await expectUnpriced(aDaiCollateral.address) // Refresh should mark status IFFY await aDaiCollateral.refresh() @@ -705,7 +672,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: staticAToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -730,7 +697,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: staticAToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -747,15 +714,6 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await zeropriceCtokenCollateral.refresh() expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) - - it('lotPrice (deprecated) is equal to price()', async () => { - const lotPrice = await aDaiCollateral.lotPrice() - const price = await aDaiCollateral.price() - expect(price.length).to.equal(2) - expect(lotPrice.length).to.equal(price.length) - expect(lotPrice[0]).to.equal(price[0]) - expect(lotPrice[1]).to.equal(price[1]) - }) }) // Note: Here the idea is to test all possible statuses and check all possible paths to default @@ -1010,9 +968,9 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime( (await aDaiCollateral.priceTimeout()) + (await aDaiCollateral.oracleTimeout()) ) - const p = await aDaiCollateral.price() - expect(p[0]).to.equal(0) - expect(p[1]).to.equal(MAX_UINT192) + const lotP = await aDaiCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) await snapshotGasCost(aDaiCollateral.refresh()) await snapshotGasCost(aDaiCollateral.refresh()) // 2nd refresh can be different than 1st }) diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index 13f8ae6777..6cc30614b8 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`] = `74354`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74365`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72686`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72697`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `72938`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `72960`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `65256`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `65278`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `74354`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `74365`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72686`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72697`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91073`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91169`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91073`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91095`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92285`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92233`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92285`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92307`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127282`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127378`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91488`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91436`; diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index e21a0f66a0..79f063c586 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -10,7 +10,6 @@ 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' @@ -101,9 +100,6 @@ 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()) @@ -289,7 +285,6 @@ 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 64458dcd1a..513e5ca5b6 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`] = `60326`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60337`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55857`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55868`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `99391`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `99413`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `91708`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `91730`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60326`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60337`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55857`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55868`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `55516`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `55527`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `55516`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `55527`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `91635`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `91657`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `91635`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `91657`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `99186`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `99208`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `91917`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `91939`; diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index 8f5bb5efe1..9f659f1b45 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -12,7 +12,6 @@ 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' @@ -61,10 +60,6 @@ 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 @@ -246,7 +241,6 @@ 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 fbc3f6874b..489f89d3df 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts @@ -277,7 +277,6 @@ 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 f7366a3fb9..afb2111ab5 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`] = `59813`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59824`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55344`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55355`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98295`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98317`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90612`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90634`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59813`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59824`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55344`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55355`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `55003`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `55014`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `55003`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `55014`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90609`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90631`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90609`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90631`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `98160`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `98182`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `90891`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `90913`; diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index dcb238c332..40465f48dc 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -1,59 +1,35 @@ import { expect } from 'chai' import hre, { ethers } from 'hardhat' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { BigNumber, ContractFactory } from 'ethers' +import { BigNumber } from 'ethers' import { useEnv } from '#/utils/env' import { getChainId } from '../../../common/blockchain-utils' -import { bn, fp, toBNDecimals } from '../../../common/numbers' -import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from './fixtures' -import { expectInIndirectReceipt } from '../../../common/events' -import { whileImpersonating } from '../../utils/impersonation' -import { IGovParams, IGovRoles, IRTokenSetup, networkConfig } from '../../../common/configuration' +import { networkConfig } from '../../../common/configuration' +import { bn, fp } from '../../../common/numbers' +import { + IERC20Metadata, + InvalidMockV3Aggregator, + MockV3Aggregator, + TestICollateral, +} from '../../../typechain' import { advanceTime, advanceBlocks, - getLatestBlockNumber, getLatestBlockTimestamp, setNextBlockTimestamp, } from '../../utils/time' -import { - MAX_UINT48, - MAX_UINT192, - MAX_UINT256, - TradeKind, - ZERO_ADDRESS, -} from '../../../common/constants' +import { MAX_UINT48, MAX_UINT192 } from '../../../common/constants' import { CollateralFixtureContext, CollateralTestSuiteFixtures, CollateralStatus, } from './pluginTestTypes' -import { - expectDecayedPrice, - expectExactPrice, - expectPrice, - expectUnpriced, -} from '../../utils/oracles' -import { - ERC20Mock, - FacadeWrite, - IAssetRegistry, - IERC20Metadata, - InvalidMockV3Aggregator, - MockV3Aggregator, - TestIBackingManager, - TestIBasketHandler, - TestICollateral, - TestIDeployer, - TestIMain, - TestIRevenueTrader, - TestIRToken, -} from '../../../typechain' +import { expectPrice, expectUnpriced } from '../../utils/oracles' import snapshotGasCost from '../../utils/snapshotGasCost' -import { IMPLEMENTATION, Implementation, ORACLE_ERROR, PRICE_TIMEOUT } from '../../fixtures' +import { IMPLEMENTATION, Implementation } from '../../fixtures' +// const describeFork = useEnv('FORK') ? describe : describe.skip const getDescribeFork = (targetNetwork = 'mainnet') => { return useEnv('FORK') && useEnv('FORK_NETWORK') === targetNetwork ? describe : describe.skip } @@ -80,7 +56,6 @@ export default function fn( itChecksTargetPerRefDefault, itChecksRefPerTokDefault, itChecksPriceChanges, - itChecksNonZeroDefaultThreshold, itHasRevenueHiding, itIsPricedByPeg, resetFork, @@ -130,12 +105,6 @@ 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) }) @@ -317,40 +286,28 @@ export default function fn( expect(newHigh).to.be.gt(initHigh) }) - it('decays for 0-valued oracle', async () => { - const initialPrice = await collateral.price() - + it('returns unpriced for 0-valued oracle', async () => { // Set price of underlying to 0 const updateAnswerTx = await chainlinkFeed.updateAnswer(0) await updateAnswerTx.wait() - // Price remains same at first, though IFFY - await collateral.refresh() - await expectExactPrice(collateral.address, initialPrice) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - - // After oracle timeout decay begins - const oracleTimeout = await collateral.oracleTimeout() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await advanceBlocks(1 + oracleTimeout / 12) - await collateral.refresh() - await expectDecayedPrice(collateral.address) - - // After price timeout it becomes unpriced - const priceTimeout = await collateral.priceTimeout() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + priceTimeout) - await advanceBlocks(1 + priceTimeout / 12) + // (0, FIX_MAX) is returned await expectUnpriced(collateral.address) - // When refreshed, sets status to DISABLED + // When refreshed, sets status to Unpriced await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await collateral.status()).to.equal(CollateralStatus.IFFY) }) - it('does not revert in case of invalid timestamp', async () => { + it('reverts in case of invalid timestamp', async () => { await chainlinkFeed.setInvalidTimestamp() - // When refreshed, sets status to IFFY + // Check price of token + const [low, high] = await collateral.price() + expect(low).to.equal(0) + expect(high).to.equal(MAX_UINT192) + + // When refreshed, sets status to Unpriced await collateral.refresh() expect(await collateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -362,29 +319,14 @@ 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 () => { @@ -419,36 +361,29 @@ export default function fn( expect(await collateral.status()).to.equal(CollateralStatus.IFFY) }) - it('decays price over priceTimeout period', async () => { + it('decays lotPrice over priceTimeout period', async () => { + // Prices should start out equal await collateral.refresh() - 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) + 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]) await advanceTime(await collateral.oracleTimeout()) // Should be roughly half, after half of priceTimeout const priceTimeout = await collateral.priceTimeout() await advanceTime(priceTimeout / 2) - 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 + lotP = await collateral.lotPrice() + expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand + expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand - // Should be unpriced after full priceTimeout + // Should be 0 after full priceTimeout await advanceTime(priceTimeout / 2) - await expectUnpriced(collateral.address) - }) - - it('lotPrice (deprecated) is equal to price()', async () => { - const lotPrice = await collateral.lotPrice() - const price = await collateral.price() - expect(price.length).to.equal(2) - expect(lotPrice.length).to.equal(price.length) - expect(lotPrice[0]).to.equal(price[0]) - expect(lotPrice[1]).to.equal(price[1]) + lotP = await collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) }) }) @@ -612,9 +547,9 @@ export default function fn( await advanceTime( (await collateral.priceTimeout()) + (await collateral.oracleTimeout()) ) - const p = await collateral.price() - expect(p[0]).to.equal(0) - expect(p[1]).to.equal(MAX_UINT192) + const lotP = await collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) }) itChecksRefPerTokDefault('after hard default', async () => { @@ -633,368 +568,5 @@ export default function fn( }) }) }) - - describe('integration tests', () => { - before(resetFork) - - let ctx: X - let owner: SignerWithAddress - let addr1: SignerWithAddress - - let chainId: number - - let defaultFixture: Fixture - - let supply: BigNumber - - // Tokens/Assets - let pairedColl: TestICollateral - let pairedERC20: ERC20Mock - let collateralERC20: IERC20Metadata - let collateral: TestICollateral - - // Core Contracts - let main: TestIMain - let rToken: TestIRToken - let assetRegistry: IAssetRegistry - let backingManager: TestIBackingManager - let basketHandler: TestIBasketHandler - let rTokenTrader: TestIRevenueTrader - - let deployer: TestIDeployer - let facadeWrite: FacadeWrite - let govParams: IGovParams - let govRoles: IGovRoles - - const config = { - dist: { - rTokenDist: bn(100), // 100% RToken - rsrDist: bn(0), // 0% RSR - }, - minTradeVolume: bn('0'), // $0 - rTokenMaxTradeVolume: MAX_UINT192, // +inf - shortFreeze: bn('259200'), // 3 days - longFreeze: bn('2592000'), // 30 days - rewardRatio: bn('1069671574938'), // approx. half life of 90 days - unstakingDelay: bn('1209600'), // 2 weeks - withdrawalLeak: fp('0'), // 0%; always refresh - warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) - tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1800'), // 30 minutes - backingBuffer: fp('0'), // 0% - maxTradeSlippage: fp('0.01'), // 1% - issuanceThrottle: { - amtRate: fp('1e6'), // 1M RToken - pctRate: fp('0.05'), // 5% - }, - redemptionThrottle: { - amtRate: fp('1e6'), // 1M RToken - pctRate: fp('0.05'), // 5% - }, - } - - interface IntegrationFixture { - ctx: X - protocol: DefaultFixture - } - - const integrationFixture: Fixture = - async function (): Promise { - return { - ctx: await loadFixture( - makeCollateralFixtureContext(owner, { maxTradeVolume: MAX_UINT192 }) - ), - protocol: await loadFixture(defaultFixture), - } - } - - before(async () => { - defaultFixture = await getDefaultFixture(collateralName) - chainId = await getChainId(hre) - if (useEnv('FORK_NETWORK').toLowerCase() === 'base') chainId = 8453 - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - ;[, owner, addr1] = await ethers.getSigners() - }) - - beforeEach(async () => { - let protocol: DefaultFixture - ;({ ctx, protocol } = await loadFixture(integrationFixture)) - ;({ collateral } = ctx) - ;({ deployer, facadeWrite, govParams } = protocol) - - supply = fp('1') - - // Create a paired collateral of the same targetName - pairedColl = await makePairedCollateral(await collateral.targetName()) - await pairedColl.refresh() - expect(await pairedColl.status()).to.equal(CollateralStatus.SOUND) - pairedERC20 = await ethers.getContractAt('ERC20Mock', await pairedColl.erc20()) - - // Prep collateral - collateralERC20 = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) - await mintCollateralTo( - ctx, - toBNDecimals(fp('1'), await collateralERC20.decimals()), - addr1, - addr1.address - ) - - // Set primary basket - const rTokenSetup: IRTokenSetup = { - assets: [], - primaryBasket: [collateral.address, pairedColl.address], - weights: [fp('0.5e-4'), fp('0.5e-4')], - backups: [], - beneficiaries: [], - } - - // Deploy RToken via FacadeWrite - const receipt = await ( - await facadeWrite.connect(owner).deployRToken( - { - name: 'RTKN RToken', - symbol: 'RTKN', - mandate: 'mandate', - params: config, - }, - rTokenSetup - ) - ).wait() - - // Get Main - const mainAddr = expectInIndirectReceipt(receipt, deployer.interface, 'RTokenCreated').args - .main - main = await ethers.getContractAt('TestIMain', mainAddr) - - // Get core contracts - assetRegistry = ( - await ethers.getContractAt('IAssetRegistry', await main.assetRegistry()) - ) - backingManager = ( - await ethers.getContractAt('TestIBackingManager', await main.backingManager()) - ) - basketHandler = ( - await ethers.getContractAt('TestIBasketHandler', await main.basketHandler()) - ) - rToken = await ethers.getContractAt('TestIRToken', await main.rToken()) - rTokenTrader = ( - await ethers.getContractAt('TestIRevenueTrader', await main.rTokenTrader()) - ) - - // Set initial governance roles - govRoles = { - owner: owner.address, - guardian: ZERO_ADDRESS, - pausers: [], - shortFreezers: [], - longFreezers: [], - } - // Setup owner and unpause - await facadeWrite.connect(owner).setupGovernance( - rToken.address, - false, // do not deploy governance - true, // unpaused - govParams, // mock values, not relevant - govRoles - ) - - // Advance past warmup period - await setNextBlockTimestamp( - (await getLatestBlockTimestamp()) + (await basketHandler.warmupPeriod()) - ) - - // Should issue - await collateralERC20.connect(addr1).approve(rToken.address, MAX_UINT256) - await pairedERC20.connect(addr1).approve(rToken.address, MAX_UINT256) - await rToken.connect(addr1).issue(supply) - }) - - it('can be put into an RToken basket', async () => { - await assetRegistry.refresh() - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - }) - - it('issues', async () => { - // Issuance in beforeEach - expect(await rToken.totalSupply()).to.equal(supply) - }) - - it('redeems', async () => { - await rToken.connect(addr1).redeem(supply) - expect(await rToken.totalSupply()).to.equal(0) - const initialCollBal = toBNDecimals(fp('1'), await collateralERC20.decimals()) - expect(await collateralERC20.balanceOf(addr1.address)).to.be.closeTo( - initialCollBal, - initialCollBal.div(bn('1e5')) // 1-part-in-100k - ) - }) - - it('rebalances out of the collateral', async () => { - // Remove collateral from basket - await basketHandler.connect(owner).setPrimeBasket([pairedERC20.address], [fp('1e-4')]) - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(anyValue, [pairedERC20.address], [fp('1e-4')], false) - await setNextBlockTimestamp( - (await getLatestBlockTimestamp()) + config.warmupPeriod.toNumber() - ) - - // Run rebalancing auction - await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, collateralERC20.address, pairedERC20.address, anyValue, anyValue) - const tradeAddr = await backingManager.trades(collateralERC20.address) - expect(tradeAddr).to.not.equal(ZERO_ADDRESS) - const trade = await ethers.getContractAt('DutchTrade', tradeAddr) - expect(await trade.sell()).to.equal(collateralERC20.address) - expect(await trade.buy()).to.equal(pairedERC20.address) - const buyAmt = await trade.bidAmount(await trade.endBlock()) - await pairedERC20.connect(addr1).approve(trade.address, buyAmt) - await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) - const pairedBal = await pairedERC20.balanceOf(backingManager.address) - await expect(trade.connect(addr1).bid()).to.emit(backingManager, 'TradeSettled') - expect(await pairedERC20.balanceOf(backingManager.address)).to.be.gt(pairedBal) - expect(await backingManager.tradesOpen()).to.equal(0) - }) - - it('forwards revenue and sells in a revenue auction', async () => { - // Send excess collateral to the RToken trader via forwardRevenue() - const mintAmt = toBNDecimals(fp('1e-6'), await collateralERC20.decimals()) - await mintCollateralTo( - ctx, - mintAmt.gt('150') ? mintAmt : bn('150'), - addr1, - backingManager.address - ) - await backingManager.forwardRevenue([collateralERC20.address]) - expect(await collateralERC20.balanceOf(rTokenTrader.address)).to.be.gt(0) - - // Run revenue auction - await expect( - rTokenTrader.manageTokens([collateralERC20.address], [TradeKind.DUTCH_AUCTION]) - ) - .to.emit(rTokenTrader, 'TradeStarted') - .withArgs(anyValue, collateralERC20.address, rToken.address, anyValue, anyValue) - const tradeAddr = await rTokenTrader.trades(collateralERC20.address) - expect(tradeAddr).to.not.equal(ZERO_ADDRESS) - const trade = await ethers.getContractAt('DutchTrade', tradeAddr) - expect(await trade.sell()).to.equal(collateralERC20.address) - expect(await trade.buy()).to.equal(rToken.address) - const buyAmt = await trade.bidAmount(await trade.endBlock()) - await rToken.connect(addr1).approve(trade.address, buyAmt) - await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) - await expect(trade.connect(addr1).bid()).to.emit(rTokenTrader, 'TradeSettled') - expect(await rTokenTrader.tradesOpen()).to.equal(0) - }) - - // === Integration Test Helpers === - - const makePairedCollateral = async (target: string): Promise => { - const onBase = useEnv('FORK_NETWORK').toLowerCase() == 'base' - const MockV3AggregatorFactory: ContractFactory = await ethers.getContractFactory( - 'MockV3Aggregator' - ) - const chainlinkFeed: MockV3Aggregator = ( - await MockV3AggregatorFactory.deploy(8, bn('1e8')) - ) - - if (target == ethers.utils.formatBytes32String('USD')) { - // USD - const erc20 = await ethers.getContractAt( - 'IERC20Metadata', - onBase ? networkConfig[chainId].tokens.USDbC! : networkConfig[chainId].tokens.USDC! - ) - const whale = onBase - ? '0xb4885bc63399bf5518b994c1d0c153334ee579d0' - : '0x40ec5b33f54e0e8a33a975908c5ba1c14e5bbbdf' - await whileImpersonating(whale, async (signer) => { - await erc20 - .connect(signer) - .transfer(addr1.address, await erc20.balanceOf(signer.address)) - }) - const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'FiatCollateral' - ) - return await FiatCollateralFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: erc20.address, - maxTradeVolume: MAX_UINT192, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.01'), // 1% - delayUntilDefault: bn('86400'), // 24h, - }) - } else if (target == ethers.utils.formatBytes32String('ETH')) { - // ETH - const erc20 = await ethers.getContractAt( - 'IERC20Metadata', - networkConfig[chainId].tokens.WETH! - ) - const whale = onBase - ? '0xb4885bc63399bf5518b994c1d0c153334ee579d0' - : '0xF04a5cC80B1E94C69B48f5ee68a08CD2F09A7c3E' - await whileImpersonating(whale, async (signer) => { - await erc20 - .connect(signer) - .transfer(addr1.address, await erc20.balanceOf(signer.address)) - }) - const SelfReferentialFactory: ContractFactory = await ethers.getContractFactory( - 'SelfReferentialCollateral' - ) - return await SelfReferentialFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: erc20.address, - maxTradeVolume: MAX_UINT192, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0'), // 0% - delayUntilDefault: bn('0'), // 0, - }) - } else if (target == ethers.utils.formatBytes32String('BTC')) { - // No official WBTC on base yet - if (onBase) throw new Error('no WBTC on base') - // BTC - const targetUnitOracle: MockV3Aggregator = ( - await MockV3AggregatorFactory.deploy(8, bn('1e8')) - ) - const erc20 = await ethers.getContractAt( - 'IERC20Metadata', - networkConfig[chainId].tokens.WBTC! - ) - await whileImpersonating('0xccf4429db6322d5c611ee964527d42e5d685dd6a', async (signer) => { - await erc20 - .connect(signer) - .transfer(addr1.address, await erc20.balanceOf(signer.address)) - }) - const NonFiatFactory: ContractFactory = await ethers.getContractFactory( - 'NonFiatCollateral' - ) - return await NonFiatFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: erc20.address, - maxTradeVolume: MAX_UINT192, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('BTC'), - defaultThreshold: fp('0.01'), // 1% - delayUntilDefault: bn('86400'), // 24h, - }, - targetUnitOracle.address, - ORACLE_TIMEOUT - ) - } else { - throw new Error(`Unknown target: ${target}`) - } - } - }) }) } diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 921b3f1bec..876b6e5e52 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -10,13 +10,7 @@ import { PRICE_TIMEOUT, REVENUE_HIDING, } from '../../../fixtures' -import { - DefaultFixture, - Fixture, - getDefaultFixture, - ORACLE_TIMEOUT, - ORACLE_TIMEOUT_PRE_BUFFER, -} from '../fixtures' +import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' import { @@ -28,12 +22,7 @@ import { IRTokenSetup, networkConfig, } from '../../../../common/configuration' -import { - CollateralStatus, - MAX_UINT48, - MAX_UINT192, - ZERO_ADDRESS, -} from '../../../../common/constants' +import { CollateralStatus, MAX_UINT48, ZERO_ADDRESS } from '../../../../common/constants' import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' import { bn, fp, toBNDecimals } from '../../../../common/numbers' import { whileImpersonating } from '../../../utils/impersonation' @@ -218,7 +207,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) @@ -241,7 +230,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -436,7 +425,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: ZERO_ADDRESS, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -472,7 +461,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -480,24 +469,6 @@ 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') }) }) @@ -695,14 +666,10 @@ 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.sub(12).toString()) + await advanceTime(ORACLE_TIMEOUT.toString()) - // 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) + // Compound + await expectUnpriced(cDaiCollateral.address) // Refresh should mark status IFFY await cDaiCollateral.refresh() @@ -718,7 +685,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -743,7 +710,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -760,15 +727,6 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await zeropriceCtokenCollateral.refresh() expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) - - it('lotPrice (deprecated) is equal to price()', async () => { - const lotPrice = await cDaiCollateral.lotPrice() - const price = await cDaiCollateral.price() - expect(price.length).to.equal(2) - expect(lotPrice.length).to.equal(price.length) - expect(lotPrice[0]).to.equal(price[0]) - expect(lotPrice[1]).to.equal(price[1]) - }) }) // Note: Here the idea is to test all possible statuses and check all possible paths to default @@ -1096,9 +1054,9 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime( (await cDaiCollateral.priceTimeout()) + (await cDaiCollateral.oracleTimeout()) ) - const p = await cDaiCollateral.price() - expect(p[0]).to.equal(0) - expect(p[1]).to.equal(MAX_UINT192) + const lotP = await cDaiCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) await snapshotGasCost(cDaiCollateral.refresh()) await snapshotGasCost(cDaiCollateral.refresh()) // 2nd refresh can be different than 1st }) diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap index 23638304fb..b0874c79c7 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`] = `119350`; +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 2`] = `117681`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117692`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `76220`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `76242`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `68538`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `68560`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119350`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119361`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117681`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117692`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `138759`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `138781`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `138685`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `138707`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139836`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139858`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139836`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139858`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `174982`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `175004`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139039`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139061`; diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 7c91bd2064..45f9e0cc8e 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -22,7 +22,6 @@ 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' @@ -120,9 +119,6 @@ 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()) @@ -357,7 +353,6 @@ const collateralSpecificStatusTests = () => { }) // Should remain SOUND after a 1% decrease - let refPerTok = await collateral.refPerTok() let currentExchangeRate = await wcusdcV3Mock.exchangeRate() await wcusdcV3Mock.setMockExchangeRate( true, @@ -366,11 +361,7 @@ 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, @@ -378,10 +369,6 @@ 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 }) } @@ -409,7 +396,6 @@ 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/CusdcV3Wrapper.test.ts b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts index 7e4d782570..cbbd48ed43 100644 --- a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts @@ -103,43 +103,12 @@ describeFork('Wrapped CUSDCv3', () => { expect(await wcusdcV3.balanceOf(don.address)).to.eq(expectedAmount) }) - it('checks for correct approval on deposit - regression test', async () => { - await expect( - wcusdcV3.connect(don).depositFrom(bob.address, charles.address, ethers.constants.MaxUint256) - ).revertedWithCustomError(wcusdcV3, 'Unauthorized') - - // Provide approval on the wrapper - await wcusdcV3.connect(bob).allow(don.address, true) - - const expectedAmount = await wcusdcV3.convertDynamicToStatic( - await cusdcV3.balanceOf(bob.address) - ) - - // This should fail even when bob approved wcusdcv3 to spend his tokens, - // because there is no explicit approval of cUSDCv3 from bob to don, only - // approval on the wrapper - await expect( - wcusdcV3.connect(don).depositFrom(bob.address, charles.address, ethers.constants.MaxUint256) - ).to.be.revertedWithCustomError(cusdcV3, 'Unauthorized') - - // Add explicit approval of cUSDCv3 and retry - await cusdcV3.connect(bob).allow(don.address, true) - await wcusdcV3 - .connect(don) - .depositFrom(bob.address, charles.address, ethers.constants.MaxUint256) - - expect(await wcusdcV3.balanceOf(bob.address)).to.eq(0) - expect(await wcusdcV3.balanceOf(charles.address)).to.eq(expectedAmount) - }) - it('deposits from a different account', async () => { expect(await wcusdcV3.balanceOf(charles.address)).to.eq(0) await expect( wcusdcV3.connect(don).depositFrom(bob.address, charles.address, ethers.constants.MaxUint256) ).revertedWithCustomError(wcusdcV3, 'Unauthorized') - - // Approval has to be on cUsdcV3, not the wrapper - await cusdcV3.connect(bob).allow(don.address, true) + await wcusdcV3.connect(bob).connect(bob).allow(don.address, true) const expectedAmount = await wcusdcV3.convertDynamicToStatic( await cusdcV3.balanceOf(bob.address) ) @@ -654,44 +623,6 @@ describeFork('Wrapped CUSDCv3', () => { expect(await compToken.balanceOf(bob.address)).to.be.greaterThan(0) }) - it('caps at balance to avoid reverts when claiming rewards (claimTo)', async () => { - const compToken = await ethers.getContractAt('ERC20Mock', COMP) - expect(await compToken.balanceOf(wcusdcV3.address)).to.equal(0) - await advanceTime(1000) - await enableRewardsAccrual(cusdcV3) - - // Accrue multiple times - for (let i = 0; i < 10; i++) { - await advanceTime(1000) - await wcusdcV3.accrue() - } - - // Get rewards from Comet - const cometRewards = await ethers.getContractAt('ICometRewards', REWARDS) - await whileImpersonating(wcusdcV3.address, async (signer) => { - await cometRewards - .connect(signer) - .claimTo(cusdcV3.address, wcusdcV3.address, wcusdcV3.address, true) - }) - - // Accrue individual account - await wcusdcV3.accrueAccount(bob.address) - - // Due to rounding, balance is smaller that owed - const owed = await wcusdcV3.getRewardOwed(bob.address) - const bal = await compToken.balanceOf(wcusdcV3.address) - expect(owed).to.be.greaterThan(bal) - - // Should still be able to claimTo (caps at balance) - const balanceBobPrev = await compToken.balanceOf(bob.address) - await expect(wcusdcV3.connect(bob).claimTo(bob.address, bob.address)).to.emit( - wcusdcV3, - 'RewardsClaimed' - ) - - expect(await compToken.balanceOf(bob.address)).to.be.greaterThan(balanceBobPrev) - }) - it('claims rewards and sends to claimer (claimRewards)', async () => { const compToken = await ethers.getContractAt('ERC20Mock', COMP) expect(await compToken.balanceOf(wcusdcV3.address)).to.equal(0) 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 d2dee358c6..a899da3b86 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`] = `109052`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109063`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `104315`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `104326`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `134449`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `134471`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `126766`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `126788`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109052`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109063`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `104315`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `104326`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `132572`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107053`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `126704`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `103985`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `126763`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `126785`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `126763`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `126785`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `134314`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `134336`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127045`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127067`; diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 1e4fe95ab9..2cd38cd51e 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -4,67 +4,29 @@ import { CurveCollateralTestSuiteFixtures, } from './pluginTestTypes' import { CollateralStatus } from '../pluginTestTypes' -import hre, { ethers } from 'hardhat' -import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { BigNumber, ContractFactory } from 'ethers' -import { getChainId } from '../../../../common/blockchain-utils' -import { bn, fp, toBNDecimals } from '../../../../common/numbers' -import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' -import { expectInIndirectReceipt } from '../../../../common/events' -import { whileImpersonating } from '../../../utils/impersonation' -import { - MAX_UINT48, - MAX_UINT192, - MAX_UINT256, - TradeKind, - ZERO_ADDRESS, - ONE_ADDRESS, -} from '../../../../common/constants' +import { ethers } from 'hardhat' +import { ERC20Mock, InvalidMockV3Aggregator } from '../../../../typechain' +import { BigNumber } from 'ethers' +import { bn, fp } from '../../../../common/numbers' +import { MAX_UINT48, ZERO_ADDRESS, ONE_ADDRESS } from '../../../../common/constants' import { expect } from 'chai' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { useEnv } from '#/utils/env' -import { expectDecayedPrice, expectExactPrice, expectUnpriced } from '../../../utils/oracles' -import { - IGovParams, - IGovRoles, - IRTokenSetup, - networkConfig, -} from '../../../../common/configuration' +import { expectUnpriced } from '../../../utils/oracles' import { advanceBlocks, advanceTime, - getLatestBlockNumber, getLatestBlockTimestamp, setNextBlockTimestamp, } from '#/test/utils/time' -import { - ERC20Mock, - FacadeWrite, - IAssetRegistry, - IERC20Metadata, - InvalidMockV3Aggregator, - MockV3Aggregator, - TestIBackingManager, - TestIBasketHandler, - TestICollateral, - TestIDeployer, - TestIMain, - TestIRevenueTrader, - TestIRToken, -} from '../../../../typechain' import snapshotGasCost from '../../../utils/snapshotGasCost' -import { IMPLEMENTATION, Implementation, ORACLE_ERROR, PRICE_TIMEOUT } from '../../../fixtures' +import { IMPLEMENTATION, Implementation } from '../../../fixtures' const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip const describeFork = useEnv('FORK') ? describe : describe.skip -const getDescribeFork = (targetNetwork = 'mainnet') => { - return useEnv('FORK') && useEnv('FORK_NETWORK') === targetNetwork ? describe : describe.skip -} - export default function fn( fixtures: CurveCollateralTestSuiteFixtures ) { @@ -430,49 +392,29 @@ export default function fn( } }) - it('decays for 0-valued oracle', async () => { - const initialPrice = await ctx.collateral.price() - - // Set price of underlyings to 0 + it('returns unpriced for 0-valued oracle', async () => { for (const feed of ctx.feeds) { await feed.updateAnswer(0).then((e) => e.wait()) } - // 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) + // (0, FIX_MAX) is returned await expectUnpriced(ctx.collateral.address) - // When refreshed, sets status to DISABLED + // When refreshed, sets status to Unpriced await ctx.collateral.refresh() - expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) }) it('does not revert in case of invalid timestamp', async () => { await ctx.feeds[0].setInvalidTimestamp() - // When refreshed, sets status to IFFY + // When refreshed, sets status to Unpriced await ctx.collateral.refresh() expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) }) - it('handles stale price', async () => { - await advanceTime( - (await ctx.collateral.oracleTimeout()) + (await ctx.collateral.priceTimeout()) - ) + it('Handles stale price', async () => { + await advanceTime(await ctx.collateral.priceTimeout()) // (0, FIX_MAX) is returned await expectUnpriced(ctx.collateral.address) @@ -482,36 +424,28 @@ export default function fn( expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) }) - 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) + 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]) await advanceTime(await ctx.collateral.oracleTimeout()) // Should be roughly half, after half of priceTimeout const priceTimeout = await ctx.collateral.priceTimeout() await advanceTime(priceTimeout / 2) - 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 + lotP = await ctx.collateral.lotPrice() + expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand + expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand // Should be 0 after full priceTimeout await advanceTime(priceTimeout / 2) - await expectUnpriced(ctx.collateral.address) - }) - - it('lotPrice (deprecated) is equal to price()', async () => { - const lotPrice = await ctx.collateral.lotPrice() - const price = await ctx.collateral.price() - expect(price.length).to.equal(2) - expect(lotPrice.length).to.equal(price.length) - expect(lotPrice[0]).to.equal(price[0]) - expect(lotPrice[1]).to.equal(price[1]) + lotP = await ctx.collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) }) }) @@ -683,8 +617,7 @@ export default function fn( expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) - // Decrease refPerTok by 1 part in a million - const refPerTok = await ctx.collateral.refPerTok() + // Decrease refPerTok by nearly 1 part in a million const currentExchangeRate = await ctx.curvePool.get_virtual_price() const newVirtualPrice = currentExchangeRate.sub(currentExchangeRate.div(bn('1e6'))).add(2) await ctx.curvePool.setVirtualPrice(newVirtualPrice) @@ -702,9 +635,6 @@ export default function fn( 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) @@ -754,9 +684,9 @@ export default function fn( await advanceTime( (await ctx.collateral.priceTimeout()) + (await ctx.collateral.oracleTimeout()) ) - const p = await ctx.collateral.price() - expect(p[0]).to.equal(0) - expect(p[1]).to.equal(MAX_UINT192) + const lotP = await ctx.collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) }) it('after hard default', async () => { @@ -778,360 +708,5 @@ export default function fn( }) }) }) - - // Only run full protocol integration tests on mainnet - // Protocol integration fixture not currently set up to deploy onto base - getDescribeFork('mainnet')('integration tests', () => { - before(resetFork) - - let ctx: X - let owner: SignerWithAddress - let addr1: SignerWithAddress - - let chainId: number - - let defaultFixture: Fixture - - let supply: BigNumber - - // Tokens/Assets - let pairedColl: TestICollateral - let pairedERC20: ERC20Mock - let collateralERC20: IERC20Metadata - let collateral: TestICollateral - - // Core Contracts - let main: TestIMain - let rToken: TestIRToken - let assetRegistry: IAssetRegistry - let backingManager: TestIBackingManager - let basketHandler: TestIBasketHandler - let rTokenTrader: TestIRevenueTrader - - let deployer: TestIDeployer - let facadeWrite: FacadeWrite - let govParams: IGovParams - let govRoles: IGovRoles - - const config = { - dist: { - rTokenDist: bn(100), // 100% RToken - rsrDist: bn(0), // 0% RSR - }, - minTradeVolume: bn('0'), // $0 - rTokenMaxTradeVolume: MAX_UINT192, // +inf - shortFreeze: bn('259200'), // 3 days - longFreeze: bn('2592000'), // 30 days - rewardRatio: bn('1069671574938'), // approx. half life of 90 days - unstakingDelay: bn('1209600'), // 2 weeks - withdrawalLeak: fp('0'), // 0%; always refresh - warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) - tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1800'), // 30 minutes - backingBuffer: fp('0'), // 0% - maxTradeSlippage: fp('0.01'), // 1% - issuanceThrottle: { - amtRate: fp('1e6'), // 1M RToken - pctRate: fp('0.05'), // 5% - }, - redemptionThrottle: { - amtRate: fp('1e6'), // 1M RToken - pctRate: fp('0.05'), // 5% - }, - } - - interface IntegrationFixture { - ctx: X - protocol: DefaultFixture - } - - const integrationFixture: Fixture = - async function (): Promise { - return { - ctx: await loadFixture( - makeCollateralFixtureContext(owner, { maxTradeVolume: MAX_UINT192 }) - ), - protocol: await loadFixture(defaultFixture), - } - } - - before(async () => { - defaultFixture = await getDefaultFixture(collateralName) - chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - ;[, owner, addr1] = await ethers.getSigners() - }) - - beforeEach(async () => { - let protocol: DefaultFixture - ;({ ctx, protocol } = await loadFixture(integrationFixture)) - ;({ collateral } = ctx) - ;({ deployer, facadeWrite, govParams } = protocol) - - supply = fp('1') - - // Create a paired collateral of the same targetName - pairedColl = await makePairedCollateral(await collateral.targetName()) - await pairedColl.refresh() - expect(await pairedColl.status()).to.equal(CollateralStatus.SOUND) - pairedERC20 = await ethers.getContractAt('ERC20Mock', await pairedColl.erc20()) - - // Prep collateral - collateralERC20 = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) - await mintCollateralTo( - ctx, - toBNDecimals(fp('1'), await collateralERC20.decimals()), - addr1, - addr1.address - ) - - // Set primary basket - const rTokenSetup: IRTokenSetup = { - assets: [], - primaryBasket: [collateral.address, pairedColl.address], - weights: [fp('0.5e-4'), fp('0.5e-4')], - backups: [], - beneficiaries: [], - } - - // Deploy RToken via FacadeWrite - const receipt = await ( - await facadeWrite.connect(owner).deployRToken( - { - name: 'RTKN RToken', - symbol: 'RTKN', - mandate: 'mandate', - params: config, - }, - rTokenSetup - ) - ).wait() - - // Get Main - const mainAddr = expectInIndirectReceipt(receipt, deployer.interface, 'RTokenCreated').args - .main - main = await ethers.getContractAt('TestIMain', mainAddr) - - // Get core contracts - assetRegistry = ( - await ethers.getContractAt('IAssetRegistry', await main.assetRegistry()) - ) - backingManager = ( - await ethers.getContractAt('TestIBackingManager', await main.backingManager()) - ) - basketHandler = ( - await ethers.getContractAt('TestIBasketHandler', await main.basketHandler()) - ) - rToken = await ethers.getContractAt('TestIRToken', await main.rToken()) - rTokenTrader = ( - await ethers.getContractAt('TestIRevenueTrader', await main.rTokenTrader()) - ) - - // Set initial governance roles - govRoles = { - owner: owner.address, - guardian: ZERO_ADDRESS, - pausers: [], - shortFreezers: [], - longFreezers: [], - } - // Setup owner and unpause - await facadeWrite.connect(owner).setupGovernance( - rToken.address, - false, // do not deploy governance - true, // unpaused - govParams, // mock values, not relevant - govRoles - ) - - // Advance past warmup period - await setNextBlockTimestamp( - (await getLatestBlockTimestamp()) + (await basketHandler.warmupPeriod()) - ) - - // Should issue - await collateralERC20.connect(addr1).approve(rToken.address, MAX_UINT256) - await pairedERC20.connect(addr1).approve(rToken.address, MAX_UINT256) - await rToken.connect(addr1).issue(supply) - }) - - it('can be put into an RToken basket', async () => { - await assetRegistry.refresh() - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - }) - - it('issues', async () => { - // Issuance in beforeEach - expect(await rToken.totalSupply()).to.equal(supply) - }) - - it('redeems', async () => { - await rToken.connect(addr1).redeem(supply) - expect(await rToken.totalSupply()).to.equal(0) - const initialCollBal = toBNDecimals(fp('1'), await collateralERC20.decimals()) - expect(await collateralERC20.balanceOf(addr1.address)).to.be.closeTo( - initialCollBal, - initialCollBal.div(bn('1e5')) // 1-part-in-100k - ) - }) - - it('rebalances out of the collateral', async () => { - // Remove collateral from basket - await basketHandler.connect(owner).setPrimeBasket([pairedERC20.address], [fp('1e-4')]) - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(anyValue, [pairedERC20.address], [fp('1e-4')], false) - await setNextBlockTimestamp( - (await getLatestBlockTimestamp()) + config.warmupPeriod.toNumber() - ) - - // Run rebalancing auction - await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, collateralERC20.address, pairedERC20.address, anyValue, anyValue) - const tradeAddr = await backingManager.trades(collateralERC20.address) - expect(tradeAddr).to.not.equal(ZERO_ADDRESS) - const trade = await ethers.getContractAt('DutchTrade', tradeAddr) - expect(await trade.sell()).to.equal(collateralERC20.address) - expect(await trade.buy()).to.equal(pairedERC20.address) - const buyAmt = await trade.bidAmount(await trade.endBlock()) - await pairedERC20.connect(addr1).approve(trade.address, buyAmt) - await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) - const pairedBal = await pairedERC20.balanceOf(backingManager.address) - await expect(trade.connect(addr1).bid()).to.emit(backingManager, 'TradeSettled') - expect(await pairedERC20.balanceOf(backingManager.address)).to.be.gt(pairedBal) - expect(await backingManager.tradesOpen()).to.equal(0) - }) - - it('forwards revenue and sells in a revenue auction', async () => { - // Send excess collateral to the RToken trader via forwardRevenue() - const mintAmt = toBNDecimals(fp('1e-6'), await collateralERC20.decimals()) - await mintCollateralTo( - ctx, - mintAmt.gt('150') ? mintAmt : bn('150'), - addr1, - backingManager.address - ) - await backingManager.forwardRevenue([collateralERC20.address]) - expect(await collateralERC20.balanceOf(rTokenTrader.address)).to.be.gt(0) - - // Run revenue auction - await expect( - rTokenTrader.manageTokens([collateralERC20.address], [TradeKind.DUTCH_AUCTION]) - ) - .to.emit(rTokenTrader, 'TradeStarted') - .withArgs(anyValue, collateralERC20.address, rToken.address, anyValue, anyValue) - const tradeAddr = await rTokenTrader.trades(collateralERC20.address) - expect(tradeAddr).to.not.equal(ZERO_ADDRESS) - const trade = await ethers.getContractAt('DutchTrade', tradeAddr) - expect(await trade.sell()).to.equal(collateralERC20.address) - expect(await trade.buy()).to.equal(rToken.address) - const buyAmt = await trade.bidAmount(await trade.endBlock()) - await rToken.connect(addr1).approve(trade.address, buyAmt) - await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) - await expect(trade.connect(addr1).bid()).to.emit(rTokenTrader, 'TradeSettled') - expect(await rTokenTrader.tradesOpen()).to.equal(0) - }) - - // === Integration Test Helpers === - - const makePairedCollateral = async (target: string): Promise => { - const MockV3AggregatorFactory: ContractFactory = await ethers.getContractFactory( - 'MockV3Aggregator' - ) - const chainlinkFeed: MockV3Aggregator = ( - await MockV3AggregatorFactory.deploy(8, bn('1e8')) - ) - - if (target == ethers.utils.formatBytes32String('USD')) { - // USD - const erc20 = await ethers.getContractAt( - 'IERC20Metadata', - networkConfig[chainId].tokens.USDC! - ) - await whileImpersonating('0x40ec5b33f54e0e8a33a975908c5ba1c14e5bbbdf', async (signer) => { - await erc20 - .connect(signer) - .transfer(addr1.address, await erc20.balanceOf(signer.address)) - }) - const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'FiatCollateral' - ) - return await FiatCollateralFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: erc20.address, - maxTradeVolume: MAX_UINT192, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.01'), // 1% - delayUntilDefault: bn('86400'), // 24h, - }) - } else if (target == ethers.utils.formatBytes32String('ETH')) { - // ETH - const erc20 = await ethers.getContractAt( - 'IERC20Metadata', - networkConfig[chainId].tokens.WETH! - ) - await whileImpersonating('0xF04a5cC80B1E94C69B48f5ee68a08CD2F09A7c3E', async (signer) => { - await erc20 - .connect(signer) - .transfer(addr1.address, await erc20.balanceOf(signer.address)) - }) - const SelfReferentialFactory: ContractFactory = await ethers.getContractFactory( - 'SelfReferentialCollateral' - ) - return await SelfReferentialFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: erc20.address, - maxTradeVolume: MAX_UINT192, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0'), // 0% - delayUntilDefault: bn('0'), // 0, - }) - } else if (target == ethers.utils.formatBytes32String('BTC')) { - // BTC - const targetUnitOracle: MockV3Aggregator = ( - await MockV3AggregatorFactory.deploy(8, bn('1e8')) - ) - const erc20 = await ethers.getContractAt( - 'IERC20Metadata', - networkConfig[chainId].tokens.WBTC! - ) - await whileImpersonating('0xccf4429db6322d5c611ee964527d42e5d685dd6a', async (signer) => { - await erc20 - .connect(signer) - .transfer(addr1.address, await erc20.balanceOf(signer.address)) - }) - const NonFiatFactory: ContractFactory = await ethers.getContractFactory( - 'NonFiatCollateral' - ) - return await NonFiatFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: erc20.address, - maxTradeVolume: MAX_UINT192, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('BTC'), - defaultThreshold: fp('0.01'), // 1% - delayUntilDefault: bn('86400'), // 24h, - }, - targetUnitOracle.address, - ORACLE_TIMEOUT - ) - } else { - throw new Error(`Unknown target: ${target}`) - } - } - }) }) } diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index bbabc4c8aa..3ff406f171 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -7,14 +7,13 @@ import { import { makeWeUSDFraxBP, mintWeUSDFraxBP, resetFork } from './helpers' import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' -import { expectDecayedPrice, expectExactPrice, expectUnpriced } from '../../../../utils/oracles' +import { 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' @@ -228,53 +227,17 @@ 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() - }) - - 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() + // Should be unpriced + await expectUnpriced(collateral.address) - // Stale should be false again - expect(await mockRTokenAsset.stale()).to.be.false + // 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]) }) } 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 4e3d02729f..ea00035779 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`] = `52713`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52705`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48245`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48237`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251771`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251539`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246889`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246657`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52713`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52705`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48245`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48237`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `79713`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47896`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `79713`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47896`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246886`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246654`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246886`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246654`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254482`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254250`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `247214`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `246982`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap index b1bc4df8a7..7c535da1b7 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`] = `485368`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69638`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `480752`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `65170`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `594734`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226883`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `589926`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `222001`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `478768`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101429`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `474226`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96961`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `544663`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `536931`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `713211`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221998`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `713581`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221998`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `701051`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209488`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `693709`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `202220`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap index ffa84bf243..5f891c6870 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`] = `61884`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61895`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57416`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57427`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `199560`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `200033`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `194678`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `195151`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61884`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61895`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57416`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57427`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57075`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57086`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57075`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57086`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `194675`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `195148`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `194675`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `195148`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `181820`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `182161`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174552`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174893`; diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index 9000295bb8..5abe5c1ec6 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -7,14 +7,13 @@ import { import { makeWeUSDFraxBP, mintWeUSDFraxBP, resetFork } from './helpers' import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' -import { expectDecayedPrice, expectExactPrice, expectUnpriced } from '../../../../utils/oracles' +import { 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' @@ -230,53 +229,17 @@ 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() - }) - - 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() + // Should be unpriced + await expectUnpriced(collateral.address) - // Stale should be false again - expect(await mockRTokenAsset.stale()).to.be.false + // 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]) }) } diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts index 8cbdd58345..c86ae829d6 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts @@ -9,6 +9,7 @@ import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' import { ERC20Mock, + IERC20, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, @@ -42,6 +43,7 @@ import { CRV, THREE_POOL_HOLDER, } from '../constants' +import { whileImpersonating } from '#/test/utils/impersonation' type Fixture = () => Promise @@ -411,6 +413,53 @@ const collateralSpecificStatusTests = () => { const finalRefPerTok = await multiFeedCollateral.refPerTok() expect(finalRefPerTok).to.equal(initialRefPerTok) }) + + it('handles shutdown correctly', async () => { + const fix = await makeW3PoolStable() + const [, alice, bob] = await ethers.getSigners() + const amount = fp('100') + const rewardPerBlock = bn('83197823300') + + const lpToken = ( + await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await fix.wrapper.curveToken() + ) + ) + const CRV = ( + await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + '0xD533a949740bb3306d119CC777fa900bA034cd52' + ) + ) + await whileImpersonating(THREE_POOL_HOLDER, async (signer) => { + await lpToken.connect(signer).transfer(alice.address, amount.mul(2)) + }) + + await lpToken.connect(alice).approve(fix.wrapper.address, ethers.constants.MaxUint256) + await fix.wrapper.connect(alice).deposit(amount, alice.address) + + // let's shutdown! + await fix.wrapper.shutdown() + + const prevBalance = await CRV.balanceOf(alice.address) + await fix.wrapper.connect(alice).claimRewards() + expect(await CRV.balanceOf(alice.address)).to.be.eq(prevBalance.add(rewardPerBlock)) + + const prevBalanceBob = await CRV.balanceOf(bob.address) + + // transfer to bob + await fix.wrapper + .connect(alice) + .transfer(bob.address, await fix.wrapper.balanceOf(alice.address)) + + await fix.wrapper.connect(bob).claimRewards() + expect(await CRV.balanceOf(bob.address)).to.be.eq(prevBalanceBob.add(rewardPerBlock)) + + await expect(fix.wrapper.connect(alice).deposit(amount, alice.address)).to.be.reverted + await expect(fix.wrapper.connect(bob).withdraw(await fix.wrapper.balanceOf(bob.address))).to.not + .be.reverted + }) } /* diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap index 7920079d2a..7ccdd8462f 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`] = `52713`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52705`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48245`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48237`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251771`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251539`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246889`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246657`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52713`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52705`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48245`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48237`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `79713`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47896`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `79713`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47896`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246886`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246654`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246886`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246654`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254482`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254250`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `247214`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `246982`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap index 876202c6a6..d4975bf94d 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`] = `485368`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69638`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `480900`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `65170`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `594734`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226883`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `589778`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `222001`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `478546`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101429`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `474004`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96961`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `544811`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `536931`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `713433`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221998`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `713507`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221998`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `701125`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209488`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `693635`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `202220`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap index 80285bbb17..20e9558ee6 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`] = `61884`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61895`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57416`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57427`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `199560`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `200033`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `194678`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `195151`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61884`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61895`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57416`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57427`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57075`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57086`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57075`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57086`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `194675`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `195148`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `194675`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `195148`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `181820`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `182161`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174552`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174893`; diff --git a/test/plugins/individual-collateral/curve/cvx/helpers.ts b/test/plugins/individual-collateral/curve/cvx/helpers.ts index a3bfbb93dc..77081254f6 100644 --- a/test/plugins/individual-collateral/curve/cvx/helpers.ts +++ b/test/plugins/individual-collateral/curve/cvx/helpers.ts @@ -71,8 +71,14 @@ export const makeW3PoolStable = async (): Promise => ) await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price()) + // Deploy external cvxMining lib + const CvxMiningFactory = await ethers.getContractFactory('CvxMining') + const cvxMining = await CvxMiningFactory.deploy() + // Deploy Wrapper - const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { + libraries: { CvxMining: cvxMining.address }, + }) const wrapper = await wrapperFactory.deploy() await wrapper.initialize(THREE_POOL_CVX_POOL_ID) @@ -118,8 +124,14 @@ export const makeWSUSDPoolStable = async (): Promise => { await realMetapool.balanceOf(MIM_THREE_POOL_HOLDER) ) + // Deploy external cvxMining lib + const CvxMiningFactory = await ethers.getContractFactory('CvxMining') + const cvxMining = await CvxMiningFactory.deploy() + // Deploy Wrapper - const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper') + const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { + libraries: { CvxMining: cvxMining.address }, + }) const wPool = await wrapperFactory.deploy() await wPool.initialize(MIM_THREE_POOL_POOL_ID) diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 4d539a4a25..7ee5c7dc01 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -12,7 +12,6 @@ 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' @@ -70,10 +69,6 @@ export const deployCollateral = async (opts: CollateralOpts = {}): Promise = () => Promise @@ -42,7 +39,8 @@ interface RSRFixture { rsr: ERC20Mock } -async function rsrFixture(chainId: number): Promise { +async function rsrFixture(): Promise { + const chainId = await getChainId(hre) const rsr: ERC20Mock = ( await ethers.getContractAt('ERC20Mock', networkConfig[chainId].tokens.RSR || '') ) @@ -74,10 +72,9 @@ export interface DefaultFixture extends RSRAndModuleFixture { export const getDefaultFixture = async function (salt: string) { const defaultFixture: Fixture = async function (): Promise { - let chainId = await getChainId(hre) - if (useEnv('FORK_NETWORK').toLowerCase() == 'base') chainId = 8453 - const { rsr } = await rsrFixture(chainId) + const { rsr } = await rsrFixture() const { gnosis } = await gnosisFixture() + const chainId = await getChainId(hre) if (!networkConfig[chainId]) { throw new Error(`Missing network configuration for ${hre.network.name}`) } diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index ea7c5554f2..8d0a4fe8e3 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -9,7 +9,6 @@ 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' @@ -129,9 +128,6 @@ 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()) @@ -256,7 +252,6 @@ 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 da1cb92e3a..b11dbcda01 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`] = `117350`; +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 2`] = `115681`; +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 hard default 1`] = `140959`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140981`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139145`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139167`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117350`; +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 2`] = `115681`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115692`; -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 1`] = `115338`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `139083`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115338`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139155`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139177`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139155`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139177`; -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 1`] = `141202`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139511`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139459`; 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`] = `117542`; +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 2`] = `115873`; +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 hard default 1`] = `141215`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141237`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139401`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139423`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117542`; +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 2`] = `115873`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115884`; -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 1`] = `115530`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `139339`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115530`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139411`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139433`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139411`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139433`; -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 1`] = `141458`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139693`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139789`; 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`] = `125832`; +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 2`] = `124163`; +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 hard default 1`] = `149997`; +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 2`] = `148183`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148135`; -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 1`] = `125843`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124163`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124174`; -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 1`] = `123820`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `148121`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `123820`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148193`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148215`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148193`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148145`; -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 1`] = `150096`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `148475`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `148427`; 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`] = `120480`; +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 2`] = `118811`; +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 hard default 1`] = `144361`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `144383`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142477`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142569`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `120480`; +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 2`] = `118811`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `118822`; -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 1`] = `118468`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `142415`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `118468`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142487`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142579`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `142557`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `142509`; -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 1`] = `144530`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `142839`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `142861`; diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index 7ac77c01d1..d47e23919f 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -12,7 +12,6 @@ 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' @@ -85,10 +84,6 @@ export const deployCollateral = async (opts: CollateralOpts = {}): Promise {} // eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificStatusTests = () => { - it('does revenue hiding correctly', async () => { + it('does revenue hiding', async () => { const MockFactory = await ethers.getContractFactory('SfraxEthMock') const erc20 = (await MockFactory.deploy()) as SfraxEthMock let currentPPS = await (await ethers.getContractAt('IsfrxEth', SFRX_ETH)).pricePerShare() @@ -215,24 +210,14 @@ const collateralSpecificStatusTests = () => { }) // Should remain SOUND after a 1% decrease - let refPerTok = await collateral.refPerTok() - const newPPS = currentPPS.sub(currentPPS.div(100)) - await erc20.setPricePerShare(newPPS) + await erc20.setPricePerShare(currentPPS.sub(currentPPS.div(100))) 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 another 1% - refPerTok = await collateral.refPerTok() - await erc20.setPricePerShare(newPPS.sub(newPPS.div(100))) + // Should become DISABLED if drops more than that + await erc20.setPricePerShare(currentPPS.sub(currentPPS.div(99))) 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 }) } @@ -259,7 +244,6 @@ 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 58a27e8317..30de100fa3 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`] = `58984`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58995`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `54247`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `54258`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59684`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59695`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58015`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58026`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `73809`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `73831`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `73809`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `73831`; diff --git a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts index 43d09c95be..cf4038f9aa 100644 --- a/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax/SFraxCollateralTestSuite.test.ts @@ -193,7 +193,6 @@ const opts = { getExpectedPrice, itClaimsRewards: it.skip, itChecksTargetPerRefDefault: it, - itChecksNonZeroDefaultThreshold: it.skip, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it.skip, diff --git a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts index 1f4213ac61..94593665b0 100644 --- a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts @@ -12,7 +12,6 @@ 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' @@ -92,11 +91,6 @@ 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 @@ -267,7 +261,6 @@ 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 7abbfa8057..dab42bfd76 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`] = `88033`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88044`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `83564`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `83575`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `132876`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `132898`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `125193`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `125215`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88033`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88044`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `83564`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `83575`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `83223`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `83234`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `83223`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `83234`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `125190`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `125212`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `125190`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `125212`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `129941`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `129963`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `125472`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `125494`; diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index 5ffecb6582..6ecee49770 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -10,7 +10,6 @@ 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, @@ -24,7 +23,7 @@ import { MorphoAaveCollateralFixtureContext, mintCollateralTo } from './mintColl import { setCode } from '@nomicfoundation/hardhat-network-helpers' import { whileImpersonating } from '#/utils/impersonation' import { whales } from '#/tasks/testing/upgrade-checker-utils/constants' -import { advanceBlocks, advanceTime } from '#/utils/time' +import { formatEther } from 'ethers/lib/utils' interface MAFiatCollateralOpts extends CollateralOpts { underlyingToken?: string @@ -35,8 +34,7 @@ interface MAFiatCollateralOpts extends CollateralOpts { const makeAaveFiatCollateralTestSuite = ( collateralName: string, - defaultCollateralOpts: MAFiatCollateralOpts, - specificTests = false + defaultCollateralOpts: MAFiatCollateralOpts ) => { const networkConfigToUse = networkConfig[31337] const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { @@ -54,6 +52,7 @@ const makeAaveFiatCollateralTestSuite = ( morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, underlyingERC20: opts.underlyingToken!, poolToken: opts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, rewardToken: networkConfigToUse.tokens.MORPHO!, }) opts.erc20 = wrapperMock.address @@ -76,9 +75,6 @@ const makeAaveFiatCollateralTestSuite = ( ) await collateral.deployed() - // Push forward chainlink feed - await pushOracleForward(opts.chainlinkFeed!) - await expect(collateral.refresh()) return collateral @@ -103,6 +99,7 @@ const makeAaveFiatCollateralTestSuite = ( morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, underlyingERC20: opts.underlyingToken!, poolToken: opts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, rewardToken: networkConfigToUse.tokens.MORPHO!, }) @@ -196,9 +193,7 @@ const makeAaveFiatCollateralTestSuite = ( */ const collateralSpecificConstructorTests = () => { it('tokenised deposits can correctly claim rewards', async () => { - const alice = hre.ethers.provider.getSigner(1) - const aliceAddress = await alice.getAddress() - + const morphoTokenOwner = '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa' const forkBlock = 17574117 const claimer = '0x05e818959c2Aa4CD05EDAe9A099c38e7Bdc377C6' const reset = getResetFork(forkBlock) @@ -211,41 +206,42 @@ const makeAaveFiatCollateralTestSuite = ( morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, underlyingERC20: defaultCollateralOpts.underlyingToken!, poolToken: defaultCollateralOpts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, rewardToken: networkConfigToUse.tokens.MORPHO!, }) - - const morphoTokenOwner = '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa' const vaultCode = await ethers.provider.getCode(usdtVault.address) await setCode(claimer, vaultCode) const vaultWithClaimableRewards = usdtVault.attach(claimer) - await whileImpersonating(hre, morphoTokenOwner, async (signer) => { - const morphoTokenInst = await ethers.getContractAt( - 'IMorphoToken', - networkConfigToUse.tokens.MORPHO!, - signer - ) - - await morphoTokenInst - .connect(signer) - .setUserRole(vaultWithClaimableRewards.address, 0, true) - }) const erc20Factory = await ethers.getContractFactory('ERC20Mock') const underlyingERC20 = erc20Factory.attach(defaultCollateralOpts.underlyingToken!) const depositAmount = utils.parseUnits('1000', 6) + const user = hre.ethers.provider.getSigner(0) + const userAddress = await user.getAddress() + + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('0.0') + await whileImpersonating( hre, whales[defaultCollateralOpts.underlyingToken!.toLowerCase()], async (whaleSigner) => { - await underlyingERC20.connect(whaleSigner).transfer(aliceAddress, depositAmount) + await underlyingERC20.connect(whaleSigner).approve(vaultWithClaimableRewards.address, 0) + await underlyingERC20 + .connect(whaleSigner) + .approve(vaultWithClaimableRewards.address, ethers.constants.MaxUint256) + await vaultWithClaimableRewards.connect(whaleSigner).mint(depositAmount, userAddress) } ) - await underlyingERC20.connect(alice).approve(vaultWithClaimableRewards.address, 0) - await underlyingERC20 - .connect(alice) - .approve(vaultWithClaimableRewards.address, ethers.constants.MaxUint256) - await vaultWithClaimableRewards.connect(alice).mint(depositAmount, aliceAddress) + + expect( + formatEther( + await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress) + ).slice(0, '8.60295466891613'.length) + ).to.be.equal('8.60295466891613') + const morphoRewards = await ethers.getContractAt( 'IMorphoRewardsDistributor', networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR! @@ -265,79 +261,47 @@ const makeAaveFiatCollateralTestSuite = ( '0xea8c2ee8d43e37ceb7b0c04d59106eff88afbe3e911b656dec7caebd415ea696', ]) - // sync needs to be called after a claim to start a new payout period - // new tokens will only be moved into pending after a _claimAssetRewards call - // which sync allows you to do without the other stuff that happens in claimRewards - await vaultWithClaimableRewards.sync() - - await advanceTime(hre, 86400 * 7) - await advanceBlocks(hre, 7200 * 7) - expect(await vaultWithClaimableRewards.connect(alice).claimRewards()) expect( - await erc20Factory.attach(networkConfigToUse.tokens.MORPHO!).balanceOf(aliceAddress) - ).to.be.eq(bn('14162082619942089266')) - }) - it('Frontrunning claiming rewards is not economical', async () => { - const alice = hre.ethers.provider.getSigner(1) - const aliceAddress = await alice.getAddress() - const bob = hre.ethers.provider.getSigner(2) - const bobAddress = await bob.getAddress() + formatEther( + await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress) + ).slice(0, '14.162082619942089'.length) + ).to.be.equal('14.162082619942089') - const MorphoTokenisedDepositFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDeposit' - ) - const ERC20Factory = await ethers.getContractFactory('ERC20Mock') - const mockRewardsToken = await ERC20Factory.deploy('MockMorphoReward', 'MMrp') - const underlyingERC20 = ERC20Factory.attach(defaultCollateralOpts.underlyingToken!) + // MORPHO is not a transferable token. + // POST Launch we could ask the Morpho team if our TokenVaults could get permission to transfer the MORPHO tokens. + // Otherwise owners of the TokenVault shares need to wait until the protocol enables the transfer function on the MORPHO token. - const vault = await MorphoTokenisedDepositFactory.deploy({ - morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, - underlyingERC20: defaultCollateralOpts.underlyingToken!, - poolToken: defaultCollateralOpts.poolToken!, - rewardToken: mockRewardsToken.address, - }) + await whileImpersonating(hre, morphoTokenOwner, async (signer) => { + const morphoTokenInst = await ethers.getContractAt( + 'IMorphoToken', + networkConfigToUse.tokens.MORPHO!, + signer + ) - const depositAmount = utils.parseUnits('1000', 6) + await morphoTokenInst + .connect(signer) + .setUserRole(vaultWithClaimableRewards.address, 0, true) + }) - await whileImpersonating( - hre, - whales[defaultCollateralOpts.underlyingToken!.toLowerCase()], - async (whaleSigner) => { - await underlyingERC20.connect(whaleSigner).transfer(aliceAddress, depositAmount) - await underlyingERC20.connect(whaleSigner).transfer(bobAddress, depositAmount.mul(10)) - } + const morphoTokenInst = await ethers.getContractAt( + 'IMorphoToken', + networkConfigToUse.tokens.MORPHO!, + user ) + expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal('0.0') - await underlyingERC20.connect(alice).approve(vault.address, ethers.constants.MaxUint256) - await vault.connect(alice).mint(depositAmount, aliceAddress) - - // Simulate inflation attack - await underlyingERC20.connect(bob).approve(vault.address, ethers.constants.MaxUint256) - await vault.connect(bob).mint(depositAmount.mul(10), bobAddress) + await vaultWithClaimableRewards.claimRewards() - await mockRewardsToken.mint(vault.address, bn('1000000000000000000000')) - await vault.sync() - - await vault.connect(bob).claimRewards() - await vault.connect(bob).redeem(depositAmount.mul(10), bobAddress, bobAddress) - - // After the inflation attack - await advanceTime(hre, 86400 * 7) - await advanceBlocks(hre, 7200 * 7) - await vault.connect(alice).claimRewards() + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('0.0') - // Shown below is that it is no longer economical to inflate own shares - // bob only managed to steal approx 1/7200 * 90% of the reward because hardhat increments block by 1 - // in practise it would be 0 as inflation attacks typically flashloan assets. - expect(await mockRewardsToken.balanceOf(aliceAddress)).to.be.closeTo( - bn('999996993746993746995'), - bn('1e15') - ) - expect(await mockRewardsToken.balanceOf(bobAddress)).to.be.closeTo( - bn('1503126503126502'), - bn('1e12') - ) + expect( + formatEther(await morphoTokenInst.balanceOf(userAddress)).slice( + 0, + '14.162082619942089'.length + ) + ).to.be.equal('14.162082619942089') }) } @@ -348,9 +312,7 @@ const makeAaveFiatCollateralTestSuite = ( const opts = { deployCollateral, - collateralSpecificConstructorTests: specificTests - ? collateralSpecificConstructorTests - : () => void 0, + collateralSpecificConstructorTests: collateralSpecificConstructorTests, collateralSpecificStatusTests, beforeEachRewardsTest, makeCollateralFixtureContext, @@ -364,7 +326,6 @@ const makeAaveFiatCollateralTestSuite = ( itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, - itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName, @@ -403,8 +364,7 @@ const makeOpts = ( const { tokens, chainlinkFeeds } = networkConfig[31337] makeAaveFiatCollateralTestSuite( 'MorphoAAVEV2FiatCollateral - USDT', - makeOpts(tokens.USDT!, tokens.aUSDT!, chainlinkFeeds.USDT!), - true // Only run specific tests once, since they are slow + makeOpts(tokens.USDT!, tokens.aUSDT!, chainlinkFeeds.USDT!) ) makeAaveFiatCollateralTestSuite( 'MorphoAAVEV2FiatCollateral - USDC', diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index 28614aff7d..c745f73174 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -15,7 +15,6 @@ 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, @@ -54,6 +53,7 @@ const makeAaveNonFiatCollateralTestSuite = ( morphoLens: configToUse.MORPHO_AAVE_LENS!, underlyingERC20: opts.underlyingToken!, poolToken: opts.poolToken!, + rewardsDistributor: configToUse.MORPHO_REWARDS_DISTRIBUTOR!, rewardToken: configToUse.tokens.MORPHO!, }) opts.erc20 = wrapperMock.address @@ -77,10 +77,6 @@ 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 @@ -104,6 +100,7 @@ const makeAaveNonFiatCollateralTestSuite = ( morphoLens: configToUse.MORPHO_AAVE_LENS!, underlyingERC20: opts.underlyingToken!, poolToken: opts.poolToken!, + rewardsDistributor: configToUse.MORPHO_REWARDS_DISTRIBUTOR!, rewardToken: configToUse.tokens.MORPHO!, }) @@ -149,18 +146,18 @@ const makeAaveNonFiatCollateralTestSuite = ( ctx: MorphoAaveCollateralFixtureContext, pctDecrease: BigNumberish ) => { - const lastRound = await ctx.chainlinkFeed!.latestRoundData() + const lastRound = await ctx.targetPrRefFeed!.latestRoundData() const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) - await ctx.chainlinkFeed!.updateAnswer(nextAnswer) + await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) } const increaseTargetPerRef = async ( ctx: MorphoAaveCollateralFixtureContext, pctIncrease: BigNumberish ) => { - const lastRound = await ctx.chainlinkFeed!.latestRoundData() + const lastRound = await ctx.targetPrRefFeed!.latestRoundData() const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) - await ctx.chainlinkFeed!.updateAnswer(nextAnswer) + await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) } const changeRefPerTok = async ( @@ -171,17 +168,25 @@ const makeAaveNonFiatCollateralTestSuite = ( await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) } + // prettier-ignore const reduceRefPerTok = async ( ctx: MorphoAaveCollateralFixtureContext, pctDecrease: BigNumberish ) => { - await changeRefPerTok(ctx, bn(pctDecrease).mul(-1)) + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) + ) } + // prettier-ignore const increaseRefPerTok = async ( ctx: MorphoAaveCollateralFixtureContext, pctIncrease: BigNumberish ) => { - await changeRefPerTok(ctx, bn(pctIncrease)) + await changeRefPerTok( + ctx, + bn(pctIncrease) + ) } const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { @@ -191,12 +196,11 @@ const makeAaveNonFiatCollateralTestSuite = ( const clRptData = await ctx.targetPrRefFeed!.latestRoundData() const clRptDecimals = await ctx.targetPrRefFeed!.decimals() - const expectedPrice = clRptData.answer - .mul(bn(10).pow(18 - clRptDecimals)) - .mul(clData.answer.mul(bn(10).pow(18 - clDecimals))) + const expctPrice = clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) .div(fp('1')) - - return expectedPrice + return expctPrice } /* @@ -208,7 +212,6 @@ const makeAaveNonFiatCollateralTestSuite = ( // eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificStatusTests = () => {} - // eslint-disable-next-line @typescript-eslint/no-empty-function const beforeEachRewardsTest = async () => {} const opts = { @@ -227,7 +230,6 @@ const makeAaveNonFiatCollateralTestSuite = ( itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, - itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, itIsPricedByPeg: true, resetFork: getResetFork(FORK_BLOCK), @@ -246,17 +248,17 @@ makeAaveNonFiatCollateralTestSuite('MorphoAAVEV2NonFiatCollateral - WBTC', { underlyingToken: configToUse.tokens.WBTC!, poolToken: configToUse.tokens.aWBTC!, priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: configToUse.chainlinkFeeds.WBTC!, - targetPrRefFeed: configToUse.chainlinkFeeds.BTC!, + chainlinkFeed: configToUse.chainlinkFeeds.BTC!, + targetPrRefFeed: configToUse.chainlinkFeeds.WBTC!, oracleTimeout: ORACLE_TIMEOUT, - refPerTokChainlinkTimeout: ORACLE_TIMEOUT.div(24), oracleError: ORACLE_ERROR, maxTradeVolume: fp('1e6'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: fp('0'), - defaultPrice: parseUnits('1', 8), - defaultRefPerTok: parseUnits('30000', 8), + defaultPrice: parseUnits('30000', 8), + defaultRefPerTok: parseUnits('1', 8), + refPerTokChainlinkTimeout: PRICE_TIMEOUT, }) makeAaveNonFiatCollateralTestSuite('MorphoAAVEV2NonFiatCollateral - stETH', { @@ -264,15 +266,15 @@ makeAaveNonFiatCollateralTestSuite('MorphoAAVEV2NonFiatCollateral - stETH', { underlyingToken: configToUse.tokens.stETH!, poolToken: configToUse.tokens.astETH!, priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: configToUse.chainlinkFeeds.stETHETH!, - targetPrRefFeed: configToUse.chainlinkFeeds.ETH!, + chainlinkFeed: configToUse.chainlinkFeeds.ETH!, + targetPrRefFeed: configToUse.chainlinkFeeds.stETHETH!, oracleTimeout: ORACLE_TIMEOUT, - refPerTokChainlinkTimeout: ORACLE_TIMEOUT.div(24), oracleError: ORACLE_ERROR, maxTradeVolume: fp('1e6'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: fp('0'), - defaultPrice: parseUnits('1', 8), - defaultRefPerTok: parseUnits('1800', 8), + defaultPrice: parseUnits('1800', 8), + defaultRefPerTok: parseUnits('1', 8), + refPerTokChainlinkTimeout: PRICE_TIMEOUT, }) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 4bf7730685..16dd346ae7 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -15,7 +15,6 @@ 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, @@ -49,6 +48,7 @@ const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise { + return formatUnits( + await instances.tokenVault + .connect(owner) + .callStatic.rewardTokenBalance(await owner.getAddress()), + 18 + ) + }, claimRewards: async (owner: Signer) => { await instances.tokenVault.connect(owner).claimRewards() }, @@ -175,8 +179,7 @@ const execTestForToken = ({ type ITestContext = ReturnType extends Promise ? U : never let context: ITestContext - before(getResetFork(FORK_BLOCK)) - + // const resetFork = getResetFork(17591000) beforeEach(async () => { context = await loadFixture(beforeEachFn) }) @@ -294,162 +297,9 @@ const execTestForToken = ({ expect(postWithdrawalBalance).lt(parseFloat(orignalBalance)) }) - it('linearly distributes rewards', async () => { - const { - users: { alice, bob, charlie }, - methods, - instances, - amountBN, - } = context - - await methods.deposit(bob, '1') - - // Enable transfers on Morpho - // ugh - await whileImpersonating( - '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa', - async (whaleSigner) => { - await whaleSigner.sendTransaction({ - to: '0x9994e35db50125e0df82e4c2dde62496ce330999', - data: '0x4b5159daa9059cbb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', - }) - await whaleSigner.sendTransaction({ - to: '0x9994e35db50125e0df82e4c2dde62496ce330999', - data: '0x4b5159da23b872dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', - }) - } - ) - - // Let's drop 700 MORPHO to the tokenVault - await whileImpersonating( - '0x6c27114E34173F8E4E7F4060a51EEb1f0120E241', - async (whaleSigner) => { - await instances.morpho - .connect(whaleSigner) - .transfer( - instances.tokenVault.address, - parseUnits('700', await instances.morpho.decimals()) - ) - } - ) - - // Account for rewards - await instances.tokenVault.sync() - - // Simulate 8 days.. - for (let i = 0; i < 8; i++) { - await advanceTime(hre, 24 * 60 * 60 - 1) - await methods.claimRewards(bob) - - if (i < 7) { - expect(await instances.morpho.balanceOf(await bob.getAddress())).to.be.closeTo( - BigNumber.from(i + 1) - .mul(100) - .mul(BigNumber.from(10).pow(await instances.morpho.decimals())), - bn('1e18') - ) - } else { - expect(await instances.morpho.balanceOf(await bob.getAddress())).to.be.closeTo( - BigNumber.from(7) - .mul(100) - .mul(BigNumber.from(10).pow(await instances.morpho.decimals())), - bn('1e18') - ) - } - } - }) - - it('linearly distributes rewards, even with multiple claims', async () => { - const { - users: { alice, bob, charlie }, - methods, - instances, - amountBN, - } = context - - await methods.deposit(bob, '1') - - // Enable transfers on Morpho - // ugh - await whileImpersonating( - '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa', - async (whaleSigner) => { - await whaleSigner.sendTransaction({ - to: '0x9994e35db50125e0df82e4c2dde62496ce330999', - data: '0x4b5159daa9059cbb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', - }) - await whaleSigner.sendTransaction({ - to: '0x9994e35db50125e0df82e4c2dde62496ce330999', - data: '0x4b5159da23b872dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', - }) - } - ) - - // Let's drop 700 MORPHO to the tokenVault - await whileImpersonating( - '0x6c27114E34173F8E4E7F4060a51EEb1f0120E241', - async (whaleSigner) => { - await instances.morpho - .connect(whaleSigner) - .transfer( - instances.tokenVault.address, - parseUnits('700', await instances.morpho.decimals()) - ) - } - ) - - // Account for rewards - await instances.tokenVault.sync() - - // Simulate 3 days.. - for (let i = 0; i < 3; i++) { - await advanceTime(hre, 24 * 60 * 60 - 1) - await methods.claimRewards(bob) - - expect(await instances.morpho.balanceOf(await bob.getAddress())).to.be.closeTo( - BigNumber.from(i + 1) - .mul(100) - .mul(BigNumber.from(10).pow(await instances.morpho.decimals())), - bn('1e18') - ) - } - - // Let's drop another 300 MORPHO to the tokenVault - await whileImpersonating( - '0x6c27114E34173F8E4E7F4060a51EEb1f0120E241', - async (whaleSigner) => { - await instances.morpho - .connect(whaleSigner) - .transfer( - instances.tokenVault.address, - parseUnits('300', await instances.morpho.decimals()) - ) - } - ) - - // Account for rewards - await instances.tokenVault.sync() - - for (let i = 3; i < 10; i++) { - await advanceTime(hre, 24 * 60 * 60 - 1) - await methods.claimRewards(bob) - - // console.log( - // 'MORPHO:', - // formatUnits( - // await instances.morpho.balanceOf(await bob.getAddress()), - // await instances.morpho.decimals() - // ) - // ) - - expect(await instances.morpho.balanceOf(await bob.getAddress())).to.be.closeTo( - BigNumber.from(i + 1) - .mul(100) - .mul(BigNumber.from(10).pow(await instances.morpho.decimals())), - bn('1e18') - ) - } - }) + /** + * There is a test for claiming rewards in the MorphoAAVEFiatCollateral.test.ts + */ }) } 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 99f876bb27..d55a1ae734 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,94 +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 ERC20 transfer 3`] = `88981`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134222`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting ERC20 transfer 4`] = `71881`; +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 1`] = `134211`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `179834`; -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 2`] = `172151`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `179812`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134222`; -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 2`] = `129753`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134211`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `129412`; -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 2`] = `129412`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `172067`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172148`; -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 2`] = `172148`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `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 SOUND 2`] = `172126`; - -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`] = `172408`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172430`; 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 ERC20 transfer 3`] = `88981`; - -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting ERC20 transfer 4`] = `71881`; +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`] = `172814`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `172836`; 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 ERC20 transfer 3`] = `88981`; - -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting ERC20 transfer 4`] = `71881`; - -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133567`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133578`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129098`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129109`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178524`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178546`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `170841`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `170863`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `133567`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `133578`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129098`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129109`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `170779`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `128768`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `170779`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `128768`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `170838`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `170860`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `170838`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `170860`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178389`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178411`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171120`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171142`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap index 26e77e6a88..de69e7ba61 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`] = `133634`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133645`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129165`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129176`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `199810`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `199843`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `192127`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `192160`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `182878`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `182889`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `178409`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `178420`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `192065`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `178079`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `192065`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `178079`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192124`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192157`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192124`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192157`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `199675`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `199708`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `192406`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `192439`; 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`] = `167266`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167277`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `162797`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `162808`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239074`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239107`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `231391`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `231424`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `222142`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `222153`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `217673`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `217684`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `231329`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `217343`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `231329`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `217343`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231388`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231421`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `231388`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `231421`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `238939`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `238972`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `231670`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `231703`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap index a420cba2b6..9148485dab 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`] = `201552`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `201563`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197083`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197094`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `217763`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `217785`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210080`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210102`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `201552`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `201563`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197083`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197094`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210077`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210099`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210077`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210099`; diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index 34bfefbb20..11b73fbaa1 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -100,9 +100,6 @@ 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 d10488770e..7b2cd8e9a5 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -12,7 +12,6 @@ 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' @@ -90,11 +89,6 @@ export const deployCollateral = async (opts: RethCollateralOpts = {}): Promise { oracleTimeouts: opts.oracleTimeouts, oracleErrors: opts.oracleErrors, lpToken: opts.lpToken, - }, - PRICE_PER_SHARE_HELPER + } ) await collateral.deployed() diff --git a/test/plugins/individual-collateral/yearnv2/constants.ts b/test/plugins/individual-collateral/yearnv2/constants.ts index 2d480c5cb4..832ccbb813 100644 --- a/test/plugins/individual-collateral/yearnv2/constants.ts +++ b/test/plugins/individual-collateral/yearnv2/constants.ts @@ -9,8 +9,6 @@ export const yvCurveUSDCcrvUSD = networkConfig['31337'].tokens.yvCurveUSDCcrvUSD export const USDP_USD_FEED = networkConfig['31337'].chainlinkFeeds.USDP as string export const CRV_USD_USD_FEED = networkConfig['31337'].chainlinkFeeds.crvUSD as string -export const PRICE_PER_SHARE_HELPER = '0x444443bae5bB8640677A8cdF94CB8879Fec948Ec' - export const YVUSDC_LP_TOKEN = '0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E' export const YVUSDP_LP_TOKEN = '0xCa978A0528116DDA3cbA9ACD3e68bc6191CA53D0' diff --git a/test/scenario/BadCollateralPlugin.test.ts b/test/scenario/BadCollateralPlugin.test.ts index 9745c962b5..ec2e04c0ee 100644 --- a/test/scenario/BadCollateralPlugin.test.ts +++ b/test/scenario/BadCollateralPlugin.test.ts @@ -27,7 +27,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -104,7 +104,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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 6b7479212c..f55d5a3652 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -34,7 +34,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -172,7 +172,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, rsr.address, MAX_TRADE_VOLUME, - ORACLE_TIMEOUT_PRE_BUFFER + ORACLE_TIMEOUT ) ) 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_PRE_BUFFER.toString(), + oracleTimeout: ORACLE_TIMEOUT.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_PRE_BUFFER.toString(), - targetUnitOracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), + oracleTimeout: ORACLE_TIMEOUT.toString(), + targetUnitOracleTimeout: ORACLE_TIMEOUT.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_PRE_BUFFER.toString(), + oracleTimeout: ORACLE_TIMEOUT.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_PRE_BUFFER.toString(), + oracleTimeout: ORACLE_TIMEOUT.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_PRE_BUFFER.toString(), - targetUnitOracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), + oracleTimeout: ORACLE_TIMEOUT.toString(), + targetUnitOracleTimeout: ORACLE_TIMEOUT.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_PRE_BUFFER.toString(), - targetUnitOracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), + oracleTimeout: ORACLE_TIMEOUT.toString(), + targetUnitOracleTimeout: ORACLE_TIMEOUT.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_PRE_BUFFER.toString(), + oracleTimeout: ORACLE_TIMEOUT.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_PRE_BUFFER.toString(), + oracleTimeout: ORACLE_TIMEOUT.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 [low] = await cETHVaultCollateral.price() - const sellAmtUnscaled = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(low) + let [, lotHigh] = await cETHVaultCollateral.lotPrice() + const sellAmtUnscaled = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(lotHigh) 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 - ;[low] = await rsrAsset.price() - const sellAmtRSR1 = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(low) + ;[, lotHigh] = await rsrAsset.lotPrice() + const sellAmtRSR1 = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(lotHigh) const buyAmtBidRSR1 = toMinBuyAmt( sellAmtRSR1, rsrPrice, diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index f1380b63f7..a3ab632140 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -28,7 +28,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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 38b11aba25..6386b158fd 100644 --- a/test/scenario/NestedRTokens.test.ts +++ b/test/scenario/NestedRTokens.test.ts @@ -22,7 +22,7 @@ import { DefaultFixture, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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 c247b1cf98..70e0fa263f 100644 --- a/test/scenario/NontrivialPeg.test.ts +++ b/test/scenario/NontrivialPeg.test.ts @@ -23,7 +23,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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 815ff2f7fb..8b1cfa00fb 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -25,7 +25,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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 a9021be240..a2a67dd94a 100644 --- a/test/scenario/SetProtocol.test.ts +++ b/test/scenario/SetProtocol.test.ts @@ -25,7 +25,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT_PRE_BUFFER, + ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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_PRE_BUFFER, + oracleTimeout: ORACLE_TIMEOUT, 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 22ad1e5662..89a9ca08e6 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12082311`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12092382`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9823907`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9830758`; -exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2436571`; +exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13653658`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13617164`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `21271957`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20897690`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10984061`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10991870`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8720813`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8713158`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6592432`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6561504`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `15036720`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `15130053`; diff --git a/test/utils/oracles.ts b/test/utils/oracles.ts index 2444878fe4..30c290f242 100644 --- a/test/utils/oracles.ts +++ b/test/utils/oracles.ts @@ -8,13 +8,6 @@ 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 ( @@ -93,15 +86,6 @@ 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 99e0f4c22f..433c9e453a 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -1,11 +1,9 @@ -import { getStorageAt, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { Decimal } from 'decimal.js' import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { expect } from 'chai' -import { TestITrading, GnosisTrade, TestIBroker } from '../../typechain' +import { TestITrading, GnosisTrade } from '../../typechain' import { bn, fp, divCeil, divRound } from '../../common/numbers' -import { IMPLEMENTATION, Implementation } from '../fixtures' export const expectTrade = async (trader: TestITrading, auctionInfo: Partial) => { if (!auctionInfo.sell) throw new Error('Must provide sell token to find trade') @@ -83,6 +81,7 @@ export const dutchBuyAmount = async ( assetInAddr: string, assetOutAddr: string, outAmount: BigNumber, + minTradeVolume: BigNumber, maxTradeSlippage: BigNumber ): Promise => { const assetIn = await ethers.getContractAt('IAsset', assetInAddr) @@ -120,23 +119,3 @@ export const dutchBuyAmount = async ( } else price = worstPrice return divCeil(outAmount.mul(price), fp('1')) } - -export const disableBatchTrade = async (broker: TestIBroker) => { - if (IMPLEMENTATION == Implementation.P1) { - const slot = await getStorageAt(broker.address, 205) - await setStorageAt(broker.address, 205, slot.replace(slot.slice(2, 14), '1'.padStart(12, '0'))) - } else { - const slot = await getStorageAt(broker.address, 56) - await setStorageAt(broker.address, 56, slot.replace(slot.slice(2, 42), '1'.padStart(40, '0'))) - } - expect(await broker.batchTradeDisabled()).to.equal(true) -} - -export const disableDutchTrade = async (broker: TestIBroker, erc20: string) => { - const mappingSlot = IMPLEMENTATION == Implementation.P1 ? bn('208') : bn('57') - const p = mappingSlot.toHexString().slice(2).padStart(64, '0') - const key = erc20.slice(2).padStart(64, '0') - const slot = ethers.utils.keccak256('0x' + key + p) - await setStorageAt(broker.address, slot, '0x' + '1'.padStart(64, '0')) - expect(await broker.dutchTradeDisabled(erc20)).to.equal(true) -}