From 566bcce27f1be4767ec9f833bace204350f82597 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 19 Mar 2024 18:41:58 -0400 Subject: [PATCH 01/10] meta-morpho/README.md --- contracts/plugins/assets/meta-morpho/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contracts/plugins/assets/meta-morpho/README.md diff --git a/contracts/plugins/assets/meta-morpho/README.md b/contracts/plugins/assets/meta-morpho/README.md new file mode 100644 index 000000000..411f16c78 --- /dev/null +++ b/contracts/plugins/assets/meta-morpho/README.md @@ -0,0 +1,3 @@ +# MetaMorpho + +Morpho Blue is a permisionless lending protocol. At the time of this writing (March 19th, 2024), the only way to deposit is through something called **MetaMorpho**: (somewhat) managed ERC4626 vaults. Our integration with these tokens is straightforward with the exception of reward claiming, which occurs via supplying a merkle proof. This can be done permisionlessly and without interacting with any of our contracts, so any interaction with rewards is omitted here. The expectation is -- _and this is important to emphasize_ -- **any MORPHO reward claiming is left up to the RToken community to cause**. MORPHO needs to be registered as an asset to liquidate the tokens afterwards. From 87e34a928a33ccc0905e84e3044f8a5a93ec671f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 19 Mar 2024 18:42:14 -0400 Subject: [PATCH 02/10] initial USDC/ETH/WBTC-supporting implementations --- .../meta-morpho/MetaMorphoFiatCollateral.sol | 45 +++++++++++++ .../MetaMorphoNonFiatCollateral.sol | 67 +++++++++++++++++++ .../MetaMorphoSelfReferentialCollateral.sol | 67 +++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol create mode 100644 contracts/plugins/assets/meta-morpho/MetaMorphoNonFiatCollateral.sol create mode 100644 contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol new file mode 100644 index 000000000..b46a9c5dc --- /dev/null +++ b/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +// solhint-disable-next-line max-line-length +import { Asset, AppreciatingFiatCollateral, CollateralConfig, IRewardable } from "../AppreciatingFiatCollateral.sol"; +import { OracleLib } from "../OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { shiftl_toFix, FIX_ONE } from "../../../libraries/Fixed.sol"; +import { IERC4626 } from "../../../vendor/oz/IERC4626.sol"; + +/** + * @title MetaMorphoFiatCollateral + * @notice Collateral plugin for a MetaMorpho vault with fiat collateral, like USDC or USDT + * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} + * + * Supports MetaMorpho ERC4626 vaults from factory 0xa9c3d3a366466fa809d1ae982fb2c46e5fc41101 + */ +contract MetaMorphoFiatCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + + uint256 private immutable oneShare; + int8 private immutable refDecimals; + + /// config.erc20 must be a MetaMorpho ERC4626 vault + /// @param config.chainlinkFeed Feed units: {UoA/ref} + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + { + require(address(config.erc20) != address(0), "missing erc20"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); + IERC4626 vault = IERC4626(address(config.erc20)); + oneShare = 10**vault.decimals(); + refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function underlyingRefPerTok() public view override returns (uint192) { + return shiftl_toFix(IERC4626(address(erc20)).convertToAssets(oneShare), -refDecimals); + } + + // Rewards happen via off-chain proofs +} diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoNonFiatCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoNonFiatCollateral.sol new file mode 100644 index 000000000..fd407d5b2 --- /dev/null +++ b/contracts/plugins/assets/meta-morpho/MetaMorphoNonFiatCollateral.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { CollateralConfig, MetaMorphoFiatCollateral } from "./MetaMorphoFiatCollateral.sol"; +import { FixLib, CEIL } from "../../../libraries/Fixed.sol"; +import { OracleLib } from "../OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +/** + * @title MetaMorphoNonFiatCollateral + * @notice Collateral plugin for a MetaMorpho vault with non-fiat collateral that + * requires two oracles, like WBTC. + * + * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} != {UoA} + */ +contract MetaMorphoNonFiatCollateral is MetaMorphoFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + AggregatorV3Interface public immutable targetUnitChainlinkFeed; // {UoA/target} + uint48 public immutable targetUnitOracleTimeout; // {s} + + /// config.erc20 must be a MetaMorpho ERC4626 vault + /// @param config.chainlinkFeed Feed units: {target/ref} + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + /// @param targetUnitChainlinkFeed_ Feed units: {UoA/target} + /// @param targetUnitOracleTimeout_ {s} oracle timeout to use for targetUnitChainlinkFeed + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + AggregatorV3Interface targetUnitChainlinkFeed_, + uint48 targetUnitOracleTimeout_ + ) MetaMorphoFiatCollateral(config, revenueHiding) { + targetUnitChainlinkFeed = targetUnitChainlinkFeed_; + targetUnitOracleTimeout = targetUnitOracleTimeout_; + maxOracleTimeout = uint48(Math.max(maxOracleTimeout, targetUnitOracleTimeout_)); + } + + /// Can revert, used by other contract functions in order to catch errors + /// @return low {UoA/tok} The low price estimate + /// @return high {UoA/tok} The high price estimate + /// @return pegPrice {target/ref} The actual price observed in the peg + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + pegPrice = chainlinkFeed.price(oracleTimeout); // {target/ref} + + // {UoA/tok} = {UoA/target} * {target/ref} * {ref/tok} + uint192 p = targetUnitChainlinkFeed.price(targetUnitOracleTimeout).mul(pegPrice).mul( + underlyingRefPerTok() + ); + uint192 err = p.mul(oracleError, CEIL); + + high = p + err; + low = p - err; + // assert(low <= high); obviously true just by inspection + } +} diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol new file mode 100644 index 000000000..765dea268 --- /dev/null +++ b/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import { AppreciatingFiatCollateral, CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { OracleLib } from "../OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { shiftl_toFix, FIX_ONE, FixLib, CEIL } from "../../../libraries/Fixed.sol"; +import { IERC4626 } from "../../../vendor/oz/IERC4626.sol"; + +/** + * @title MetaMorphoSelfReferentialCollateral + * @notice Collateral plugin for a MetaMorpho vault with self referential collateral, like WETH + * Expected: {tok} == {ref}, {ref} == {target}, {target} != {UoA} + */ +contract MetaMorphoSelfReferentialCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + uint256 private immutable oneShare; + int8 private immutable refDecimals; + + /// config.erc20 must be a MetaMorpho ERC4626 vault + /// @param config.chainlinkFeed Feed units: {UoA/ref} + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + { + require(config.defaultThreshold == 0, "default threshold not supported"); + require(address(config.erc20) != address(0), "missing erc20"); + IERC4626 vault = IERC4626(address(config.erc20)); + oneShare = 10**vault.decimals(); + refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); + } + + /// Can revert, used by other contract functions in order to catch errors + /// @return low {UoA/tok} The low price estimate + /// @return high {UoA/tok} The high price estimate + /// @return pegPrice {target/ref} + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + // {UoA/tok} = {UoA/ref} * {ref/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(underlyingRefPerTok()); + uint192 err = p.mul(oracleError, CEIL); + + low = p - err; + high = p + err; + // assert(low <= high); obviously true just by inspection + + pegPrice = targetPerRef(); + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function underlyingRefPerTok() public view override returns (uint192) { + return shiftl_toFix(IERC4626(address(erc20)).convertToAssets(oneShare), -refDecimals); + } +} From 1a8cbcec79bdd254f16d4fccbb26b84983264acb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 19 Mar 2024 20:10:26 -0400 Subject: [PATCH 03/10] new structure for better inheritance --- .../assets/meta-morpho/IMetaMorpho.sol | 20 ++++ .../meta-morpho/MetaMorphoCollateral.sol | 91 +++++++++++++++++++ .../meta-morpho/MetaMorphoFiatCollateral.sol | 32 +------ .../MetaMorphoNonFiatCollateral.sol | 67 -------------- .../MetaMorphoSelfReferentialCollateral.sol | 30 ++---- .../plugins/assets/meta-morpho/README.md | 26 +++++- 6 files changed, 148 insertions(+), 118 deletions(-) create mode 100644 contracts/plugins/assets/meta-morpho/IMetaMorpho.sol create mode 100644 contracts/plugins/assets/meta-morpho/MetaMorphoCollateral.sol delete mode 100644 contracts/plugins/assets/meta-morpho/MetaMorphoNonFiatCollateral.sol diff --git a/contracts/plugins/assets/meta-morpho/IMetaMorpho.sol b/contracts/plugins/assets/meta-morpho/IMetaMorpho.sol new file mode 100644 index 000000000..6c3562986 --- /dev/null +++ b/contracts/plugins/assets/meta-morpho/IMetaMorpho.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +// solhint-disable-next-line max-line-length +import { Asset, AppreciatingFiatCollateral, CollateralConfig, IRewardable } from "../AppreciatingFiatCollateral.sol"; +import { OracleLib } from "../OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { shiftl_toFix, FIX_ONE } from "../../../libraries/Fixed.sol"; +import { IERC4626 } from "../../../vendor/oz/IERC4626.sol"; + +interface IMetaMorpho is IERC4626 { + function lastTotalAssets() external view returns (uint256); + + function fee() external view returns (uint96); + + // solhint-disable-next-line func-name-mixedcase + function DECIMALS_OFFSET() external view returns (uint8); +} diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoCollateral.sol new file mode 100644 index 000000000..58619f4c9 --- /dev/null +++ b/contracts/plugins/assets/meta-morpho/MetaMorphoCollateral.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +// solhint-disable-next-line max-line-length +import { Asset, AppreciatingFiatCollateral, CollateralConfig, IRewardable } from "../AppreciatingFiatCollateral.sol"; +import { OracleLib } from "../OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { divuu } from "../../../libraries/Fixed.sol"; +import { IMetaMorpho } from "./IMetaMorpho.sol"; + +uint256 constant WAD = 1e18; + +/** + * @title MetaMorphoFiatCollateral + * @notice Collateral plugin for a MetaMorpho vault. Does not handle reward claiming. + * + * Supports MetaMorpho ERC4626 vaults from factory 0xa9c3d3a366466fa809d1ae982fb2c46e5fc41101 + */ +abstract contract MetaMorphoCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + + uint256 private immutable oneShare; + int8 private immutable refDecimals; + uint256 private immutable sharePerAsset; // {qTok/qRef} + + /// config.erc20 must be a MetaMorpho ERC4626 vault + /// @param config.chainlinkFeed Feed units: {UoA/ref} + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + { + require(address(config.erc20) != address(0), "missing erc20"); + IMetaMorpho vault = IMetaMorpho(address(config.erc20)); + oneShare = 10**vault.decimals(); + refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); + sharePerAsset = 10**vault.DECIMALS_OFFSET(); + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function underlyingRefPerTok() public view override returns (uint192) { + // Approach: Build fees into our measure of refPerTok to prevent downturns after fee claim + + // {qTok}, {qRef} + (uint256 feeShares, uint256 newTotalAssets) = _getAccruedFeeShares(); + + // {qTok} + uint256 newTotalShares = IMetaMorpho(address(erc20)).totalSupply() + feeShares; + return divuu(sharePerAsset * newTotalAssets, newTotalShares); + } + + // Rewards happen via off-chain proofs + + // === Internal === + + /// Compute how many new shares _would_ minted if fees were extracted, as well as totalAssets() + /// @dev Computes and returns the fee shares (`feeShares`) to mint and the new vault's total assets + /// @return feeShares {qTok} + /// @return newTotalAssets {qRef} + function _getAccruedFeeShares() + internal + view + returns (uint256 feeShares, uint256 newTotalAssets) + { + // This function modeled after vault internal function `_accruedFeeShares()` + + IMetaMorpho vault = IMetaMorpho(address(erc20)); + newTotalAssets = vault.totalAssets(); // {qRef} + uint256 lastTotalAssets = vault.lastTotalAssets(); // {qRef} + + uint256 totalInterest = newTotalAssets > lastTotalAssets + ? newTotalAssets - lastTotalAssets + : 0; // {qRef} + + if (totalInterest != 0) { + uint96 fee = vault.fee(); + if (fee != 0) { + // It is acknowledged that `feeAssets` may be rounded down to 0 if `totalInterest * fee < WAD`. + uint256 feeAssets = (totalInterest * fee) / WAD; // {qRef} + newTotalAssets = newTotalAssets - feeAssets; // {qRef} + + // The fee assets is subtracted from the total assets in this calculation to compensate for the fact + // that total assets is already increased by the total interest (including the fee assets). + feeShares = + (feeAssets * (vault.totalSupply() + sharePerAsset)) / + (newTotalAssets + 1); // {qTok} + } + } + } +} diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol index b46a9c5dc..cccfa43a2 100644 --- a/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol +++ b/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol @@ -1,45 +1,21 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.19; -// solhint-disable-next-line max-line-length -import { Asset, AppreciatingFiatCollateral, CollateralConfig, IRewardable } from "../AppreciatingFiatCollateral.sol"; -import { OracleLib } from "../OracleLib.sol"; -// solhint-disable-next-line max-line-length -import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { shiftl_toFix, FIX_ONE } from "../../../libraries/Fixed.sol"; -import { IERC4626 } from "../../../vendor/oz/IERC4626.sol"; +import { CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { MetaMorphoCollateral } from "./MetaMorphoCollateral.sol"; /** * @title MetaMorphoFiatCollateral * @notice Collateral plugin for a MetaMorpho vault with fiat collateral, like USDC or USDT * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} - * - * Supports MetaMorpho ERC4626 vaults from factory 0xa9c3d3a366466fa809d1ae982fb2c46e5fc41101 */ -contract MetaMorphoFiatCollateral is AppreciatingFiatCollateral { - using OracleLib for AggregatorV3Interface; - - uint256 private immutable oneShare; - int8 private immutable refDecimals; - +contract MetaMorphoFiatCollateral is MetaMorphoCollateral { /// config.erc20 must be a MetaMorpho ERC4626 vault /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) - AppreciatingFiatCollateral(config, revenueHiding) + MetaMorphoCollateral(config, revenueHiding) { - require(address(config.erc20) != address(0), "missing erc20"); require(config.defaultThreshold > 0, "defaultThreshold zero"); - IERC4626 vault = IERC4626(address(config.erc20)); - oneShare = 10**vault.decimals(); - refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); } - - /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens - function underlyingRefPerTok() public view override returns (uint192) { - return shiftl_toFix(IERC4626(address(erc20)).convertToAssets(oneShare), -refDecimals); - } - - // Rewards happen via off-chain proofs } diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoNonFiatCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoNonFiatCollateral.sol deleted file mode 100644 index fd407d5b2..000000000 --- a/contracts/plugins/assets/meta-morpho/MetaMorphoNonFiatCollateral.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { CollateralConfig, MetaMorphoFiatCollateral } from "./MetaMorphoFiatCollateral.sol"; -import { FixLib, CEIL } from "../../../libraries/Fixed.sol"; -import { OracleLib } from "../OracleLib.sol"; -// solhint-disable-next-line max-line-length -import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; - -/** - * @title MetaMorphoNonFiatCollateral - * @notice Collateral plugin for a MetaMorpho vault with non-fiat collateral that - * requires two oracles, like WBTC. - * - * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} != {UoA} - */ -contract MetaMorphoNonFiatCollateral is MetaMorphoFiatCollateral { - using OracleLib for AggregatorV3Interface; - using FixLib for uint192; - - AggregatorV3Interface public immutable targetUnitChainlinkFeed; // {UoA/target} - uint48 public immutable targetUnitOracleTimeout; // {s} - - /// config.erc20 must be a MetaMorpho ERC4626 vault - /// @param config.chainlinkFeed Feed units: {target/ref} - /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide - /// @param targetUnitChainlinkFeed_ Feed units: {UoA/target} - /// @param targetUnitOracleTimeout_ {s} oracle timeout to use for targetUnitChainlinkFeed - constructor( - CollateralConfig memory config, - uint192 revenueHiding, - AggregatorV3Interface targetUnitChainlinkFeed_, - uint48 targetUnitOracleTimeout_ - ) MetaMorphoFiatCollateral(config, revenueHiding) { - targetUnitChainlinkFeed = targetUnitChainlinkFeed_; - targetUnitOracleTimeout = targetUnitOracleTimeout_; - maxOracleTimeout = uint48(Math.max(maxOracleTimeout, targetUnitOracleTimeout_)); - } - - /// Can revert, used by other contract functions in order to catch errors - /// @return low {UoA/tok} The low price estimate - /// @return high {UoA/tok} The high price estimate - /// @return pegPrice {target/ref} The actual price observed in the peg - function tryPrice() - external - view - override - returns ( - uint192 low, - uint192 high, - uint192 pegPrice - ) - { - pegPrice = chainlinkFeed.price(oracleTimeout); // {target/ref} - - // {UoA/tok} = {UoA/target} * {target/ref} * {ref/tok} - uint192 p = targetUnitChainlinkFeed.price(targetUnitOracleTimeout).mul(pegPrice).mul( - underlyingRefPerTok() - ); - uint192 err = p.mul(oracleError, CEIL); - - high = p + err; - low = p - err; - // assert(low <= high); obviously true just by inspection - } -} diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol index 765dea268..77a52afee 100644 --- a/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol +++ b/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol @@ -3,36 +3,27 @@ pragma solidity 0.8.19; // solhint-disable-next-line max-line-length import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -import { AppreciatingFiatCollateral, CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { FixLib, CEIL } from "../../../libraries/Fixed.sol"; import { OracleLib } from "../OracleLib.sol"; -// solhint-disable-next-line max-line-length -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { shiftl_toFix, FIX_ONE, FixLib, CEIL } from "../../../libraries/Fixed.sol"; -import { IERC4626 } from "../../../vendor/oz/IERC4626.sol"; +import { MetaMorphoCollateral } from "./MetaMorphoCollateral.sol"; /** - * @title MetaMorphoSelfReferentialCollateral + * @title MetaMorphoNonFiatCollateral * @notice Collateral plugin for a MetaMorpho vault with self referential collateral, like WETH * Expected: {tok} == {ref}, {ref} == {target}, {target} != {UoA} */ -contract MetaMorphoSelfReferentialCollateral is AppreciatingFiatCollateral { - using OracleLib for AggregatorV3Interface; +contract MetaMorphoNonFiatCollateral is MetaMorphoCollateral { using FixLib for uint192; - - uint256 private immutable oneShare; - int8 private immutable refDecimals; + using OracleLib for AggregatorV3Interface; /// config.erc20 must be a MetaMorpho ERC4626 vault /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) - AppreciatingFiatCollateral(config, revenueHiding) + MetaMorphoCollateral(config, revenueHiding) { - require(config.defaultThreshold == 0, "default threshold not supported"); - require(address(config.erc20) != address(0), "missing erc20"); - IERC4626 vault = IERC4626(address(config.erc20)); - oneShare = 10**vault.decimals(); - refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); + // require(config.defaultThreshold > 0, "defaultThreshold zero"); } /// Can revert, used by other contract functions in order to catch errors @@ -59,9 +50,4 @@ contract MetaMorphoSelfReferentialCollateral is AppreciatingFiatCollateral { pegPrice = targetPerRef(); } - - /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens - function underlyingRefPerTok() public view override returns (uint192) { - return shiftl_toFix(IERC4626(address(erc20)).convertToAssets(oneShare), -refDecimals); - } } diff --git a/contracts/plugins/assets/meta-morpho/README.md b/contracts/plugins/assets/meta-morpho/README.md index 411f16c78..05ff60e99 100644 --- a/contracts/plugins/assets/meta-morpho/README.md +++ b/contracts/plugins/assets/meta-morpho/README.md @@ -1,3 +1,27 @@ # MetaMorpho -Morpho Blue is a permisionless lending protocol. At the time of this writing (March 19th, 2024), the only way to deposit is through something called **MetaMorpho**: (somewhat) managed ERC4626 vaults. Our integration with these tokens is straightforward with the exception of reward claiming, which occurs via supplying a merkle proof. This can be done permisionlessly and without interacting with any of our contracts, so any interaction with rewards is omitted here. The expectation is -- _and this is important to emphasize_ -- **any MORPHO reward claiming is left up to the RToken community to cause**. MORPHO needs to be registered as an asset to liquidate the tokens afterwards. +Morpho Blue is a permisionless lending protocol. At the time of this writing (March 19th, 2024), the only way to deposit is through something called **MetaMorpho**: (somewhat) managed ERC4626 vaults. Our integration with these tokens is straightforward with the exception of reward claiming, which occurs via supplying a merkle proof. This can be done permisionlessly and without interacting with any of our contracts, so any interaction with rewards is omitted here. The expectation is -- _and this is important to emphasize_ -- **any MORPHO reward claiming is left up to the RToken community to cause**. + +## Up-only-ness + +MetaMorpho suffers from a similar to that of the Curve volatile pools which can lose assets on admin fee claim. + +## Target tokens + +**USD** +| Name | Symbol | Address | Reward Tokens | +| -- | -- | -- | -- | +| Steakhouse USDC | steakUSDC| 0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB | wstETH, MORPHO | +| Steakhouse PYSUD | steakPYUSD | 0xbEEF02e5E13584ab96848af90261f0C8Ee04722a | MORPHO | +| Flagship USDT | bbUSDT| 0x2C25f6C25770fFEC5959D34B94Bf898865e5D6b1 | MORPHO | + +**ETH** + +| Name | Symbol | Address | Reward Tokens | +| -------- | ------- | ------------------------------------------ | --------------------------- | +| Re7 WETH | Re7WETH | 0x78Fc2c2eD1A4cDb5402365934aE5648aDAd094d0 | USDC, SWISE, BTRFLY, MORPHO | + +## Future Work + +- Assets need to exist for each of the Reward Tokens +- The right reward token assets need to be registered for an RToken as a function of their collateral. This can be done using the above table. From c6fb3a5e62a8e442a073705b26083e552783b656 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 20 Mar 2024 17:35:03 -0400 Subject: [PATCH 04/10] refine approach and get tests for 4 vaults passing --- common/configuration.ts | 14 ++ .../plugins/assets/ERC4626FiatCollateral.sol | 39 +++ .../assets/meta-morpho/IMetaMorpho.sol | 20 -- .../meta-morpho/MetaMorphoCollateral.sol | 91 ------- .../meta-morpho/MetaMorphoFiatCollateral.sol | 8 +- .../MetaMorphoSelfReferentialCollateral.sol | 10 +- .../plugins/assets/meta-morpho/README.md | 2 +- .../plugins/mocks/MockMetaMorpho4626.sol | 47 ++++ .../MetaMorphoFiatCollateral.test.ts | 222 ++++++++++++++++++ ...etaMorphoSelfReferentialCollateral.test.ts | 208 ++++++++++++++++ .../meta-morpho/constants.ts | 30 +++ .../meta-morpho/mintCollateralTo.ts | 45 ++++ 12 files changed, 617 insertions(+), 119 deletions(-) create mode 100644 contracts/plugins/assets/ERC4626FiatCollateral.sol delete mode 100644 contracts/plugins/assets/meta-morpho/IMetaMorpho.sol delete mode 100644 contracts/plugins/assets/meta-morpho/MetaMorphoCollateral.sol create mode 100644 contracts/plugins/mocks/MockMetaMorpho4626.sol create mode 100644 test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts create mode 100644 test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts create mode 100644 test/plugins/individual-collateral/meta-morpho/constants.ts create mode 100644 test/plugins/individual-collateral/meta-morpho/mintCollateralTo.ts diff --git a/common/configuration.ts b/common/configuration.ts index 22dc12056..6ad55b1ff 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -70,6 +70,8 @@ export interface ITokens { sUSDT?: string sETH?: string MORPHO?: string + SWISE?: string + BTRFLY?: string astETH?: string wsgUSDC?: string wsgUSDbC?: string @@ -87,6 +89,12 @@ export interface ITokens { maWBTC?: string maWETH?: string maStETH?: string + + // MetaMorpho + steakUSDC?: string + bbUSDT?: string + steakPYUSD?: string + Re7WETH?: string } export interface IFeeds { @@ -202,11 +210,17 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', astETH: '0x1982b2F5814301d4e9a8b0201555376e62F82428', MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', + SWISE: '0x48C3399719B582dD63eB5AADf12A40B4C3f52FA2', + BTRFLY: '0xc55126051B22eBb829D00368f4B12Bde432de5Da', yvCurveUSDPcrvUSD: '0xF56fB6cc29F0666BDD1662FEaAE2A3C935ee3469', yvCurveUSDCcrvUSD: '0x7cA00559B978CFde81297849be6151d3ccB408A9', pyUSD: '0x6c3ea9036406852006290770bedfcaba0e23a0e8', aEthPyUSD: '0x0C0d01AbF3e6aDfcA0989eBbA9d6e85dD58EaB1E', saEthPyUSD: '0x00F2a835758B33f3aC53516Ebd69f3dc77B0D152', // canonical wrapper + steakUSDC: '0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB', + steakPYUSD: '0xbEEF02e5E13584ab96848af90261f0C8Ee04722a', + bbUSDT: '0x2C25f6C25770fFEC5959D34B94Bf898865e5D6b1', + Re7WETH: '0x78Fc2c2eD1A4cDb5402365934aE5648aDAd094d0', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', diff --git a/contracts/plugins/assets/ERC4626FiatCollateral.sol b/contracts/plugins/assets/ERC4626FiatCollateral.sol new file mode 100644 index 000000000..1d7fcaeb2 --- /dev/null +++ b/contracts/plugins/assets/ERC4626FiatCollateral.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +// solhint-disable-next-line max-line-length +import { Asset, AppreciatingFiatCollateral, CollateralConfig, IRewardable } from "./AppreciatingFiatCollateral.sol"; +import { OracleLib } from "./OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import { IERC4626 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { shiftl_toFix } from "../../libraries/Fixed.sol"; + +/** + * @title ERC4626FiatCollateral + * @notice Collateral plugin for a ERC4626 vault + */ +contract ERC4626FiatCollateral is AppreciatingFiatCollateral { + uint256 private immutable oneShare; + int8 private immutable refDecimals; + + /// config.erc20 must be a MetaMorpho ERC4626 vault + /// @param config.chainlinkFeed Feed units: {UoA/ref} + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + { + require(address(config.erc20) != address(0), "missing erc20"); + // require(config.defaultThreshold > 0, "defaultThreshold zero"); + IERC4626 vault = IERC4626(address(config.erc20)); + oneShare = 10**vault.decimals(); + refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function underlyingRefPerTok() public view override returns (uint192) { + // already accounts for fees to be taken out + return shiftl_toFix(IERC4626(address(erc20)).convertToAssets(oneShare), -refDecimals); + } +} diff --git a/contracts/plugins/assets/meta-morpho/IMetaMorpho.sol b/contracts/plugins/assets/meta-morpho/IMetaMorpho.sol deleted file mode 100644 index 6c3562986..000000000 --- a/contracts/plugins/assets/meta-morpho/IMetaMorpho.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -// solhint-disable-next-line max-line-length -import { Asset, AppreciatingFiatCollateral, CollateralConfig, IRewardable } from "../AppreciatingFiatCollateral.sol"; -import { OracleLib } from "../OracleLib.sol"; -// solhint-disable-next-line max-line-length -import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { shiftl_toFix, FIX_ONE } from "../../../libraries/Fixed.sol"; -import { IERC4626 } from "../../../vendor/oz/IERC4626.sol"; - -interface IMetaMorpho is IERC4626 { - function lastTotalAssets() external view returns (uint256); - - function fee() external view returns (uint96); - - // solhint-disable-next-line func-name-mixedcase - function DECIMALS_OFFSET() external view returns (uint8); -} diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoCollateral.sol deleted file mode 100644 index 58619f4c9..000000000 --- a/contracts/plugins/assets/meta-morpho/MetaMorphoCollateral.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -// solhint-disable-next-line max-line-length -import { Asset, AppreciatingFiatCollateral, CollateralConfig, IRewardable } from "../AppreciatingFiatCollateral.sol"; -import { OracleLib } from "../OracleLib.sol"; -// solhint-disable-next-line max-line-length -import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { divuu } from "../../../libraries/Fixed.sol"; -import { IMetaMorpho } from "./IMetaMorpho.sol"; - -uint256 constant WAD = 1e18; - -/** - * @title MetaMorphoFiatCollateral - * @notice Collateral plugin for a MetaMorpho vault. Does not handle reward claiming. - * - * Supports MetaMorpho ERC4626 vaults from factory 0xa9c3d3a366466fa809d1ae982fb2c46e5fc41101 - */ -abstract contract MetaMorphoCollateral is AppreciatingFiatCollateral { - using OracleLib for AggregatorV3Interface; - - uint256 private immutable oneShare; - int8 private immutable refDecimals; - uint256 private immutable sharePerAsset; // {qTok/qRef} - - /// config.erc20 must be a MetaMorpho ERC4626 vault - /// @param config.chainlinkFeed Feed units: {UoA/ref} - /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide - constructor(CollateralConfig memory config, uint192 revenueHiding) - AppreciatingFiatCollateral(config, revenueHiding) - { - require(address(config.erc20) != address(0), "missing erc20"); - IMetaMorpho vault = IMetaMorpho(address(config.erc20)); - oneShare = 10**vault.decimals(); - refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); - sharePerAsset = 10**vault.DECIMALS_OFFSET(); - } - - /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens - function underlyingRefPerTok() public view override returns (uint192) { - // Approach: Build fees into our measure of refPerTok to prevent downturns after fee claim - - // {qTok}, {qRef} - (uint256 feeShares, uint256 newTotalAssets) = _getAccruedFeeShares(); - - // {qTok} - uint256 newTotalShares = IMetaMorpho(address(erc20)).totalSupply() + feeShares; - return divuu(sharePerAsset * newTotalAssets, newTotalShares); - } - - // Rewards happen via off-chain proofs - - // === Internal === - - /// Compute how many new shares _would_ minted if fees were extracted, as well as totalAssets() - /// @dev Computes and returns the fee shares (`feeShares`) to mint and the new vault's total assets - /// @return feeShares {qTok} - /// @return newTotalAssets {qRef} - function _getAccruedFeeShares() - internal - view - returns (uint256 feeShares, uint256 newTotalAssets) - { - // This function modeled after vault internal function `_accruedFeeShares()` - - IMetaMorpho vault = IMetaMorpho(address(erc20)); - newTotalAssets = vault.totalAssets(); // {qRef} - uint256 lastTotalAssets = vault.lastTotalAssets(); // {qRef} - - uint256 totalInterest = newTotalAssets > lastTotalAssets - ? newTotalAssets - lastTotalAssets - : 0; // {qRef} - - if (totalInterest != 0) { - uint96 fee = vault.fee(); - if (fee != 0) { - // It is acknowledged that `feeAssets` may be rounded down to 0 if `totalInterest * fee < WAD`. - uint256 feeAssets = (totalInterest * fee) / WAD; // {qRef} - newTotalAssets = newTotalAssets - feeAssets; // {qRef} - - // The fee assets is subtracted from the total assets in this calculation to compensate for the fact - // that total assets is already increased by the total interest (including the fee assets). - feeShares = - (feeAssets * (vault.totalSupply() + sharePerAsset)) / - (newTotalAssets + 1); // {qTok} - } - } - } -} diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol index cccfa43a2..a365bd21e 100644 --- a/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol +++ b/contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol @@ -2,19 +2,21 @@ pragma solidity 0.8.19; import { CollateralConfig } from "../AppreciatingFiatCollateral.sol"; -import { MetaMorphoCollateral } from "./MetaMorphoCollateral.sol"; +import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol"; /** * @title MetaMorphoFiatCollateral * @notice Collateral plugin for a MetaMorpho vault with fiat collateral, like USDC or USDT * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} + * + * For example: steakUSDC, steakPYUSD, bbUSDT */ -contract MetaMorphoFiatCollateral is MetaMorphoCollateral { +contract MetaMorphoFiatCollateral is ERC4626FiatCollateral { /// config.erc20 must be a MetaMorpho ERC4626 vault /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) - MetaMorphoCollateral(config, revenueHiding) + ERC4626FiatCollateral(config, revenueHiding) { require(config.defaultThreshold > 0, "defaultThreshold zero"); } diff --git a/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol b/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol index 77a52afee..74a3b3c71 100644 --- a/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol +++ b/contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol @@ -6,14 +6,16 @@ import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/ import { CollateralConfig } from "../AppreciatingFiatCollateral.sol"; import { FixLib, CEIL } from "../../../libraries/Fixed.sol"; import { OracleLib } from "../OracleLib.sol"; -import { MetaMorphoCollateral } from "./MetaMorphoCollateral.sol"; +import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol"; /** - * @title MetaMorphoNonFiatCollateral + * @title MetaMorphoSelfReferentialCollateral * @notice Collateral plugin for a MetaMorpho vault with self referential collateral, like WETH * Expected: {tok} == {ref}, {ref} == {target}, {target} != {UoA} + * + * For example: Re7WETH */ -contract MetaMorphoNonFiatCollateral is MetaMorphoCollateral { +contract MetaMorphoSelfReferentialCollateral is ERC4626FiatCollateral { using FixLib for uint192; using OracleLib for AggregatorV3Interface; @@ -21,7 +23,7 @@ contract MetaMorphoNonFiatCollateral is MetaMorphoCollateral { /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) - MetaMorphoCollateral(config, revenueHiding) + ERC4626FiatCollateral(config, revenueHiding) { // require(config.defaultThreshold > 0, "defaultThreshold zero"); } diff --git a/contracts/plugins/assets/meta-morpho/README.md b/contracts/plugins/assets/meta-morpho/README.md index 05ff60e99..10a980fc6 100644 --- a/contracts/plugins/assets/meta-morpho/README.md +++ b/contracts/plugins/assets/meta-morpho/README.md @@ -23,5 +23,5 @@ MetaMorpho suffers from a similar to that of the Curve volatile pools which can ## Future Work -- Assets need to exist for each of the Reward Tokens +- Assets need to exist for each of the Reward Tokens, which requires oracles. Only USDC meets this bar; SWISE, BTRFLY, and MORPHO do not have oracles yet. - The right reward token assets need to be registered for an RToken as a function of their collateral. This can be done using the above table. diff --git a/contracts/plugins/mocks/MockMetaMorpho4626.sol b/contracts/plugins/mocks/MockMetaMorpho4626.sol new file mode 100644 index 000000000..c71af1218 --- /dev/null +++ b/contracts/plugins/mocks/MockMetaMorpho4626.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { IERC4626 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "../../libraries/Fixed.sol"; + +// Simple pass-through wrapper for real MetaMorpho ERC4626 vaults +// Allows settable asset count for testing +contract MockMetaMorpho4626 { + using FixLib for uint192; + + IERC4626 public immutable actual; // the real ERC4626 vault + + uint192 public multiplier = FIX_ONE; + + // solhint-disable-next-line no-empty-blocks + constructor(IERC4626 _actual) { + actual = _actual; + } + + function applyMultiple(uint192 multiple) external { + multiplier = multiplier.mul(multiple); + } + + // === Pass-throughs === + + function balanceOf(address account) external view returns (uint256) { + return actual.balanceOf(account); + } + + function asset() external view returns (address) { + return actual.asset(); + } + + function decimals() external view returns (uint8) { + return actual.decimals(); + } + + function convertToAssets(uint256 amount) external view returns (uint256) { + return multiplier.mulu_toUint(actual.convertToAssets(amount), CEIL); + } + + function totalAssets() public view returns (uint256) { + return multiplier.mulu_toUint(actual.totalAssets(), CEIL); + } +} diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts new file mode 100644 index 000000000..06055babf --- /dev/null +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoFiatCollateral.test.ts @@ -0,0 +1,222 @@ +import { networkConfig } from '#/common/configuration' +import { bn, fp } from '#/common/numbers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { TestICollateral } from '@typechain/TestICollateral' +import { MockV3Aggregator__factory } from '@typechain/index' +import { expect } from 'chai' +import { BigNumber, BigNumberish, ContractFactory } from 'ethers' +import { ethers } from 'hardhat' +import collateralTests from '../collateralTests' +import { getResetFork } from '../helpers' +import { CollateralOpts, CollateralFixtureContext } from '../pluginTestTypes' +import { pushOracleForward } from '../../../utils/oracles' +import { MAX_UINT192 } from '#/common/constants' +import { + DELAY_UNTIL_DEFAULT, + FORK_BLOCK, + PYUSD_ORACLE_ERROR, + PYUSD_ORACLE_TIMEOUT, + USDT_ORACLE_TIMEOUT, + USDT_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + PRICE_TIMEOUT, +} from './constants' +import { mintCollateralTo } from './mintCollateralTo' + +interface MAFiatCollateralOpts extends CollateralOpts { + defaultPrice?: BigNumberish + defaultRefPerTok?: BigNumberish +} + +const makeFiatCollateralTestSuite = ( + collateralName: string, + defaultCollateralOpts: MAFiatCollateralOpts, + specificTests = false +) => { + const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { + opts = { ...defaultCollateralOpts, ...opts } + + const MetaMorphoCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'MetaMorphoFiatCollateral' + ) + const collateral = await MetaMorphoCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + + // Push forward chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + + await expect(collateral.refresh()) + + return collateral + } + + type Fixture = () => Promise + + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} + ): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) + opts.chainlinkFeed = chainlinkFeed.address + + // Hack: use wrapped vault by default unless the maxTradeVolume is infinite, in which + // case the mock would break things. Care! Fragile! + if (!opts.maxTradeVolume || !MAX_UINT192.eq(opts.maxTradeVolume)) { + const mockMetaMorphoFactory = await ethers.getContractFactory('MockMetaMorpho4626') + const mockERC4626 = await mockMetaMorphoFactory.deploy(opts.erc20!) + opts.erc20 = mockERC4626.address + } + + const collateral = await deployCollateral({ ...opts }) + const tok = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) + return { + alice, + collateral, + chainlinkFeed, + tok, + } as CollateralFixtureContext + } + + return makeCollateralFixtureContext + } + + const reduceTargetPerRef = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const increaseTargetPerRef = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const reduceRefPerTok = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const mockERC4626 = await ethers.getContractAt('MockMetaMorpho4626', ctx.tok.address) + await mockERC4626.applyMultiple(bn('100').sub(pctDecrease).mul(fp('1')).div(100)) + } + + const increaseRefPerTok = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const mockERC4626 = await ethers.getContractAt('MockMetaMorpho4626', ctx.tok.address) + await mockERC4626.applyMultiple(bn('100').add(pctIncrease).mul(fp('1')).div(100)) + } + + const getExpectedPrice = async (ctx: CollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(refPerTok) + .div(fp('1')) + } + + /* + Define collateral-specific tests + */ + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificConstructorTests = () => {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificStatusTests = () => {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const beforeEachRewardsTest = async () => {} + + const opts = { + deployCollateral, + collateralSpecificConstructorTests: specificTests + ? collateralSpecificConstructorTests + : () => void 0, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it, + itChecksTargetPerRefDefaultUp: it, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName, + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, + itIsPricedByPeg: true, + toleranceDivisor: bn('1e9'), // 1 part in 1 billion + } + + collateralTests(opts) +} + +const makeOpts = ( + vault: string, + chainlinkFeed: string, + oracleTimeout: BigNumber, + oracleError: BigNumber +): MAFiatCollateralOpts => { + return { + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + oracleTimeout: oracleTimeout, + oracleError: oracleError, + defaultThreshold: oracleError.add(fp('0.01')), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + maxTradeVolume: fp('1e6'), + revenueHiding: fp('0'), + defaultPrice: bn('1e8'), + defaultRefPerTok: fp('1'), + erc20: vault, + chainlinkFeed, + } +} + +/* + Run the test suite +*/ +const { tokens, chainlinkFeeds } = networkConfig[31337] +makeFiatCollateralTestSuite( + 'MetaMorphoFiatCollateral - steakUSDC', + makeOpts(tokens.steakUSDC!, chainlinkFeeds.USDC!, USDC_ORACLE_TIMEOUT, USDC_ORACLE_ERROR) +) +makeFiatCollateralTestSuite( + 'MetaMorphoFiatCollateral - steakPYUSD', + makeOpts(tokens.steakPYUSD!, chainlinkFeeds.pyUSD!, PYUSD_ORACLE_TIMEOUT, PYUSD_ORACLE_ERROR), + true // Only run specific tests once, since they are slow +) +makeFiatCollateralTestSuite( + 'MetaMorphoFiatCollateral - bbUSDT', + makeOpts(tokens.bbUSDT!, chainlinkFeeds.USDT!, USDT_ORACLE_TIMEOUT, USDT_ORACLE_ERROR), + true // Only run specific tests once, since they are slow +) diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts new file mode 100644 index 000000000..4595b569d --- /dev/null +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts @@ -0,0 +1,208 @@ +import { networkConfig } from '#/common/configuration' +import { bn, fp } from '#/common/numbers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { TestICollateral } from '@typechain/TestICollateral' +import { MockV3Aggregator__factory } from '@typechain/index' +import { expect } from 'chai' +import { BigNumber, BigNumberish, ContractFactory } from 'ethers' +import { ethers } from 'hardhat' +import collateralTests from '../collateralTests' +import { getResetFork } from '../helpers' +import { CollateralOpts, CollateralFixtureContext } from '../pluginTestTypes' +import { pushOracleForward } from '../../../utils/oracles' +import { MAX_UINT192 } from '#/common/constants' +import { + DELAY_UNTIL_DEFAULT, + FORK_BLOCK, + ETH_ORACLE_ERROR, + ETH_ORACLE_TIMEOUT, + PRICE_TIMEOUT, +} from './constants' +import { mintCollateralTo } from './mintCollateralTo' + +interface MAFiatCollateralOpts extends CollateralOpts { + defaultPrice?: BigNumberish + defaultRefPerTok?: BigNumberish +} + +const makeFiatCollateralTestSuite = ( + collateralName: string, + defaultCollateralOpts: MAFiatCollateralOpts, + specificTests = false +) => { + const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { + opts = { ...defaultCollateralOpts, ...opts } + + const MetaMorphoCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'MetaMorphoSelfReferentialCollateral' + ) + const collateral = await MetaMorphoCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + + // Push forward chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + + await expect(collateral.refresh()) + + return collateral + } + + type Fixture = () => Promise + + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} + ): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) + opts.chainlinkFeed = chainlinkFeed.address + + // Hack: use wrapped vault by default unless the maxTradeVolume is infinite, in which + // case the mock would break things. Care! Fragile! + if (!opts.maxTradeVolume || !MAX_UINT192.eq(opts.maxTradeVolume)) { + const mockMetaMorphoFactory = await ethers.getContractFactory('MockMetaMorpho4626') + const mockERC4626 = await mockMetaMorphoFactory.deploy(opts.erc20!) + opts.erc20 = mockERC4626.address + } + + const collateral = await deployCollateral({ ...opts }) + const tok = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) + return { + alice, + collateral, + chainlinkFeed, + tok, + } as CollateralFixtureContext + } + + return makeCollateralFixtureContext + } + + const reduceTargetPerRef = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const increaseTargetPerRef = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const reduceRefPerTok = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const mockERC4626 = await ethers.getContractAt('MockMetaMorpho4626', ctx.tok.address) + await mockERC4626.applyMultiple(bn('100').sub(pctDecrease).mul(fp('1')).div(100)) + } + + const increaseRefPerTok = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const mockERC4626 = await ethers.getContractAt('MockMetaMorpho4626', ctx.tok.address) + await mockERC4626.applyMultiple(bn('100').add(pctIncrease).mul(fp('1')).div(100)) + } + + const getExpectedPrice = async (ctx: CollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(refPerTok) + .div(fp('1')) + } + + /* + Define collateral-specific tests + */ + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificConstructorTests = () => {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificStatusTests = () => {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const beforeEachRewardsTest = async () => {} + + const opts = { + deployCollateral, + collateralSpecificConstructorTests: specificTests + ? collateralSpecificConstructorTests + : () => void 0, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefaultUp: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it.skip, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName, + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, + itIsPricedByPeg: true, + toleranceDivisor: bn('1e9'), // 1 part in 1 billion + } + + collateralTests(opts) +} + +const makeOpts = ( + vault: string, + chainlinkFeed: string, + oracleTimeout: BigNumber, + oracleError: BigNumber +): MAFiatCollateralOpts => { + return { + targetName: ethers.utils.formatBytes32String('ETH'), + priceTimeout: PRICE_TIMEOUT, + oracleTimeout: oracleTimeout, + oracleError: oracleError, + defaultThreshold: oracleError.add(fp('0.01')), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + maxTradeVolume: fp('1e6'), + revenueHiding: fp('0'), + defaultPrice: bn('4000e8'), + defaultRefPerTok: fp('1'), + erc20: vault, + chainlinkFeed, + } +} + +/* + Run the test suite +*/ +const { tokens, chainlinkFeeds } = networkConfig[31337] +makeFiatCollateralTestSuite( + 'MetaMorphoSelfReferentialCollateral - Re7WETH', + makeOpts(tokens.Re7WETH!, chainlinkFeeds.ETH!, ETH_ORACLE_TIMEOUT, ETH_ORACLE_ERROR) +) diff --git a/test/plugins/individual-collateral/meta-morpho/constants.ts b/test/plugins/individual-collateral/meta-morpho/constants.ts new file mode 100644 index 000000000..ec71a3b34 --- /dev/null +++ b/test/plugins/individual-collateral/meta-morpho/constants.ts @@ -0,0 +1,30 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' + +// Mainnet Addresses + +// USDC +export const USDC_USD_FEED = networkConfig['1'].chainlinkFeeds.USDC! +export const USDC_ORACLE_TIMEOUT = bn('86400') +export const USDC_ORACLE_ERROR = fp('0.0025') + +// PYUSD +export const PYUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.pyUSD! +export const PYUSD_ORACLE_TIMEOUT = bn('86400') +export const PYUSD_ORACLE_ERROR = fp('0.003') + +// USDT +export const USDT_USD_FEED = networkConfig['1'].chainlinkFeeds.USDT! +export const USDT_ORACLE_TIMEOUT = bn('86400') +export const USDT_ORACLE_ERROR = fp('0.0025') + +// ETH +export const ETH_USD_FEED = networkConfig['1'].chainlinkFeeds.ETH! +export const ETH_ORACLE_TIMEOUT = bn('3600') +export const ETH_ORACLE_ERROR = fp('0.005') + +// General +export const PRICE_TIMEOUT = bn(604800) // 1 week +export const DELAY_UNTIL_DEFAULT = bn(86400) + +export const FORK_BLOCK = 19463181 diff --git a/test/plugins/individual-collateral/meta-morpho/mintCollateralTo.ts b/test/plugins/individual-collateral/meta-morpho/mintCollateralTo.ts new file mode 100644 index 000000000..099a9fbf8 --- /dev/null +++ b/test/plugins/individual-collateral/meta-morpho/mintCollateralTo.ts @@ -0,0 +1,45 @@ +import { networkConfig } from '#/common/configuration' +import { CollateralFixtureContext, MintCollateralFunc } from '../pluginTestTypes' +import hre, { ethers } from 'hardhat' +import { BigNumberish } from 'ethers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { whileImpersonating } from '#/utils/impersonation' + +export const whales: { [key: string]: string } = { + [networkConfig['31337'].tokens.steakUSDC!]: '0xC977d218Fde6A39c7aCE71C8243545c276B48931', + [networkConfig['31337'].tokens.steakPYUSD!]: '0x7E4B4DC22111B84594d9b7707A8DCFFd793D477A', + [networkConfig['31337'].tokens.bbUSDT!]: '0xc8E3C36a72B9AA4Af0a057eb4A11e1AFC16465bB', + [networkConfig['31337'].tokens.Re7WETH!]: '0xd553294B42bdFEb49D8f5A64E8B2D3A65fc673A9', +} + +/** + * Mint collateral to a recipient using a whale. + * @param ctx The CollateralFixtureContext object. + * @param amount The amount of collateral to mint. + * @param _ The signer with address (not used in this function). + * @param recipient The address of the recipient of the minted collateral. + */ +export const mintCollateralTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + _: SignerWithAddress, + recipient: string +) => { + const tok = await ethers.getContractAt('MockMetaMorpho4626', ctx.tok.address) + + // It can be a MockMetaMorpho4626 or the real ERC4626 + try { + // treat it as a wrapper to begin + const underlying = await ethers.getContractAt('IERC20Metadata', await tok.actual()) + + // Transfer the underlying (real) ERC4626; wrapper is pass-through + await whileImpersonating(hre, whales[underlying.address], async (whaleSigner) => { + await underlying.connect(whaleSigner).transfer(recipient, amount) + }) + } catch { + // if we error out, then it's not the wrapper we're dealing with + await whileImpersonating(hre, whales[ctx.tok.address], async (whaleSigner) => { + await ctx.tok.connect(whaleSigner).transfer(recipient, amount) + }) + } +} From 11357bf2ecfda236e796cd469e4a20e68cb14c1f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 20 Mar 2024 18:00:45 -0400 Subject: [PATCH 05/10] deploy scripts --- scripts/deploy.ts | 6 +- .../collaterals/deploy_bbusdt.ts | 91 +++++++++++++++++++ .../collaterals/deploy_re7weth.ts | 91 +++++++++++++++++++ .../collaterals/deploy_steakpyusd.ts | 91 +++++++++++++++++++ .../collaterals/deploy_steakusdc.ts | 91 +++++++++++++++++++ 5 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_re7weth.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_steakusdc.ts diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 8cf827c40..3e0aeaa22 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -72,7 +72,11 @@ async function main() { 'phase2-assets/collaterals/deploy_yearn_v2_curve_usdc.ts', 'phase2-assets/collaterals/deploy_yearn_v2_curve_usdp.ts', 'phase2-assets/collaterals/deploy_sfrax.ts', - 'phase2-assets/collaterals/deploy_sfrax_eth.ts' + 'phase2-assets/collaterals/deploy_sfrax_eth.ts', + 'phase2-assets/collaterals/deploy_steakusdc.ts', + 'phase2-assets/collaterals/deploy_steakpyusd.ts', + 'phase2-assets/collaterals/deploy_bbusdt.ts', + 'phase2-assets/collaterals/deploy_re7weth.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts b/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts new file mode 100644 index 000000000..afbc877fd --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts @@ -0,0 +1,91 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + USDT_ORACLE_TIMEOUT, + USDT_ORACLE_ERROR, + USDT_USD_FEED, + PRICE_TIMEOUT, + DELAY_UNTIL_DEFAULT, +} from '../../../../test/plugins/individual-collateral/meta-morpho/constants' +import { MetaMorphoFiatCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy MetaMorpho Flagship USDT - bbUSDT **************************/ + + const MetaMorphoFiatCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'MetaMorphoFiatCollateral' + ) + + const collateral = await MetaMorphoFiatCollateralFactory.connect( + deployer + ).deploy( + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: USDT_USD_FEED, + oracleError: USDT_ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.bbUSDT, + maxTradeVolume: fp('1e6').toString(), + oracleTimeout: USDT_ORACLE_TIMEOUT.toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: USDT_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), + }, + fp('1e-6') // small admin fee uncertainty + ) + await collateral.deployed() + + console.log(`Deployed bbUSDT to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.bbUSDT = collateral.address + assetCollDeployments.erc20s.bbUSDT = networkConfig[chainId].tokens.bbUSDT + 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_re7weth.ts b/scripts/deployment/phase2-assets/collaterals/deploy_re7weth.ts new file mode 100644 index 000000000..6377a415a --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_re7weth.ts @@ -0,0 +1,91 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, + ETH_USD_FEED, + PRICE_TIMEOUT, + DELAY_UNTIL_DEFAULT, +} from '../../../../test/plugins/individual-collateral/meta-morpho/constants' +import { MetaMorphoSelfReferentialCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy MetaMorpho RE7 Labs ETH - Re7WETH **************************/ + + const MetaMorphoFiatCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'MetaMorphoSelfReferentialCollateral' + ) + + const collateral = ( + await MetaMorphoFiatCollateralFactory.connect(deployer).deploy( + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: ETH_USD_FEED, + oracleError: ETH_ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.Re7WETH, + maxTradeVolume: fp('1e6').toString(), + oracleTimeout: ETH_ORACLE_TIMEOUT.toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: ETH_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), + }, + fp('1e-6') // small admin fee uncertainty + ) + ) + await collateral.deployed() + + console.log(`Deployed Re7WETH to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.Re7WETH = collateral.address + assetCollDeployments.erc20s.Re7WETH = networkConfig[chainId].tokens.Re7WETH + 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_steakpyusd.ts b/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts new file mode 100644 index 000000000..850c21040 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts @@ -0,0 +1,91 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + PYUSD_ORACLE_TIMEOUT, + PYUSD_ORACLE_ERROR, + PYUSD_USD_FEED, + PRICE_TIMEOUT, + DELAY_UNTIL_DEFAULT, +} from '../../../../test/plugins/individual-collateral/meta-morpho/constants' +import { MetaMorphoFiatCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy MetaMorpho Steakhouse PYUSD - steakPYUSD **************************/ + + const MetaMorphoFiatCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'MetaMorphoFiatCollateral' + ) + + const collateral = await MetaMorphoFiatCollateralFactory.connect( + deployer + ).deploy( + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: PYUSD_USD_FEED, + oracleError: PYUSD_ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.steakPYUSD, + maxTradeVolume: fp('1e6').toString(), + oracleTimeout: PYUSD_ORACLE_TIMEOUT.toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: PYUSD_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), + }, + fp('1e-6') // small admin fee uncertainty + ) + await collateral.deployed() + + console.log(`Deployed steakPYUSD to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.steakPYUSD = collateral.address + assetCollDeployments.erc20s.steakPYUSD = networkConfig[chainId].tokens.steakPYUSD + 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_steakusdc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_steakusdc.ts new file mode 100644 index 000000000..e8dcedbd9 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_steakusdc.ts @@ -0,0 +1,91 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + USDC_USD_FEED, + PRICE_TIMEOUT, + DELAY_UNTIL_DEFAULT, +} from '../../../../test/plugins/individual-collateral/meta-morpho/constants' +import { MetaMorphoFiatCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy MetaMorpho Steakhouse USDC - steakUSDC **************************/ + + const MetaMorphoFiatCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'MetaMorphoFiatCollateral' + ) + + const collateral = await MetaMorphoFiatCollateralFactory.connect( + deployer + ).deploy( + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: USDC_USD_FEED, + oracleError: USDC_ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.steakUSDC, + maxTradeVolume: fp('1e6').toString(), + oracleTimeout: USDC_ORACLE_TIMEOUT.toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: USDC_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), + }, + fp('1e-6') // small admin fee uncertainty + ) + await collateral.deployed() + + console.log(`Deployed steakUSDC to ${hre.network.name} (${chainId}): ${collateral.address}`) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.steakUSDC = collateral.address + assetCollDeployments.erc20s.steakUSDC = networkConfig[chainId].tokens.steakUSDC + 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 +}) From 861c89c34bc12545f9673cb0b43bc2ec36a4ca88 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 20 Mar 2024 18:03:59 -0400 Subject: [PATCH 06/10] verification scripts --- .../collateral-plugins/verify_re7weth.ts | 60 +++++++++++++++++++ .../collateral-plugins/verify_steakusdc.ts | 60 +++++++++++++++++++ scripts/verify_etherscan.ts | 4 +- 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 scripts/verification/collateral-plugins/verify_re7weth.ts create mode 100644 scripts/verification/collateral-plugins/verify_steakusdc.ts diff --git a/scripts/verification/collateral-plugins/verify_re7weth.ts b/scripts/verification/collateral-plugins/verify_re7weth.ts new file mode 100644 index 000000000..8d695a08e --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_re7weth.ts @@ -0,0 +1,60 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { fp } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { + ETH_ORACLE_TIMEOUT, + ETH_ORACLE_ERROR, + ETH_USD_FEED, + PRICE_TIMEOUT, + DELAY_UNTIL_DEFAULT, +} from '../../../test/plugins/individual-collateral/meta-morpho/constants' +import { verifyContract } from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify Re7WETH **************************/ + await verifyContract( + chainId, + deployments.collateral.Re7WETH, + [ + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: ETH_USD_FEED, + oracleError: ETH_ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.Re7WETH, + maxTradeVolume: fp('1e6').toString(), + oracleTimeout: ETH_ORACLE_TIMEOUT.toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: ETH_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), + }, + fp('1e-6'), // small admin fee uncertainty + ], + 'contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol:MetaMorphoSelfReferentialCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_steakusdc.ts b/scripts/verification/collateral-plugins/verify_steakusdc.ts new file mode 100644 index 000000000..277f30b6a --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_steakusdc.ts @@ -0,0 +1,60 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { fp } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + USDC_USD_FEED, + PRICE_TIMEOUT, + DELAY_UNTIL_DEFAULT, +} from '../../../test/plugins/individual-collateral/meta-morpho/constants' +import { verifyContract } from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify steakUSDC **************************/ + await verifyContract( + chainId, + deployments.collateral.steakUSDC, + [ + { + priceTimeout: PRICE_TIMEOUT.toString(), + chainlinkFeed: USDC_USD_FEED, + oracleError: USDC_ORACLE_ERROR.toString(), + erc20: networkConfig[chainId].tokens.steakUSDC, + maxTradeVolume: fp('1e6').toString(), + oracleTimeout: USDC_ORACLE_TIMEOUT.toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: USDC_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule + delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), + }, + fp('1e-6'), // small admin fee uncertainty + ], + 'contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol:MetaMorphoFiatCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index 13ddca24b..36f8fdd53 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -71,7 +71,9 @@ async function main() { 'collateral-plugins/verify_yearn_v2_curve_usdc.ts', 'collateral-plugins/verify_yearn_v2_curve_usdp.ts', 'collateral-plugins/verify_sfrax.ts', - 'collateral-plugins/verify_sfrax_eth.ts' + 'collateral-plugins/verify_sfrax_eth.ts', + 'collateral-plugins/verify_steakusdc.ts', + 'collateral-plugins/verify_re7weth.ts' ) } else if (chainId == '8453' || chainId == '84531') { // Base L2 chains From 36a79813a5ad5fccf9a551a13a83cb300dba0c99 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 20 Mar 2024 18:07:27 -0400 Subject: [PATCH 07/10] adjust maxTradeVolume for smaller vaults --- scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts | 2 +- scripts/deployment/phase2-assets/collaterals/deploy_re7weth.ts | 2 +- .../deployment/phase2-assets/collaterals/deploy_steakpyusd.ts | 2 +- .../deployment/phase2-assets/collaterals/deploy_steakusdc.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts b/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts index afbc877fd..85b8a6b14 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts @@ -60,7 +60,7 @@ async function main() { chainlinkFeed: USDT_USD_FEED, oracleError: USDT_ORACLE_ERROR.toString(), erc20: networkConfig[chainId].tokens.bbUSDT, - maxTradeVolume: fp('1e6').toString(), + maxTradeVolume: fp('1e6').toString(), // $7.5m vault oracleTimeout: USDT_ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: USDT_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_re7weth.ts b/scripts/deployment/phase2-assets/collaterals/deploy_re7weth.ts index 6377a415a..717ae7d32 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_re7weth.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_re7weth.ts @@ -59,7 +59,7 @@ async function main() { chainlinkFeed: ETH_USD_FEED, oracleError: ETH_ORACLE_ERROR.toString(), erc20: networkConfig[chainId].tokens.Re7WETH, - maxTradeVolume: fp('1e6').toString(), + maxTradeVolume: fp('1e6').toString(), // $12m vault oracleTimeout: ETH_ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: ETH_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts b/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts index 850c21040..b85431973 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts @@ -60,7 +60,7 @@ async function main() { chainlinkFeed: PYUSD_USD_FEED, oracleError: PYUSD_ORACLE_ERROR.toString(), erc20: networkConfig[chainId].tokens.steakPYUSD, - maxTradeVolume: fp('1e6').toString(), + maxTradeVolume: fp('0.5e6').toString(), // $1.7m vault oracleTimeout: PYUSD_ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: PYUSD_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_steakusdc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_steakusdc.ts index e8dcedbd9..5d8ae1a02 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_steakusdc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_steakusdc.ts @@ -60,7 +60,7 @@ async function main() { chainlinkFeed: USDC_USD_FEED, oracleError: USDC_ORACLE_ERROR.toString(), erc20: networkConfig[chainId].tokens.steakUSDC, - maxTradeVolume: fp('1e6').toString(), + maxTradeVolume: fp('1e6').toString(), // 17m vault oracleTimeout: USDC_ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: USDC_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule From ed1f34f1fa68793f05d67f9f52b1239bf1cf5282 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 20 Mar 2024 18:08:25 -0400 Subject: [PATCH 08/10] add comment --- contracts/plugins/assets/ERC4626FiatCollateral.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/plugins/assets/ERC4626FiatCollateral.sol b/contracts/plugins/assets/ERC4626FiatCollateral.sol index 1d7fcaeb2..c8e1284d6 100644 --- a/contracts/plugins/assets/ERC4626FiatCollateral.sol +++ b/contracts/plugins/assets/ERC4626FiatCollateral.sol @@ -13,6 +13,8 @@ import { shiftl_toFix } from "../../libraries/Fixed.sol"; /** * @title ERC4626FiatCollateral * @notice Collateral plugin for a ERC4626 vault + * + * Warning: Only valid for linear ERC4626 vaults */ contract ERC4626FiatCollateral is AppreciatingFiatCollateral { uint256 private immutable oneShare; From 6ccf0dcad1647742196c29b30eb5d762d89fa67f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 20 Mar 2024 20:58:36 -0400 Subject: [PATCH 09/10] fix test --- .../meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts index 4595b569d..769ca2916 100644 --- a/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/meta-morpho/MetaMorphoSelfReferentialCollateral.test.ts @@ -170,7 +170,7 @@ const makeFiatCollateralTestSuite = ( collateralName, chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, itIsPricedByPeg: true, - toleranceDivisor: bn('1e9'), // 1 part in 1 billion + toleranceDivisor: bn('1e8'), // 1 part in 100 million } collateralTests(opts) From 86ace8a205bca99ef3fdcedbcc827b7a4444c2be Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 22 Mar 2024 13:26:12 -0400 Subject: [PATCH 10/10] decrease maxTradeVolume for bbUSDT and steakPYUSD --- scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts | 2 +- .../deployment/phase2-assets/collaterals/deploy_steakpyusd.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts b/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts index 85b8a6b14..034a22aa5 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_bbusdt.ts @@ -60,7 +60,7 @@ async function main() { chainlinkFeed: USDT_USD_FEED, oracleError: USDT_ORACLE_ERROR.toString(), erc20: networkConfig[chainId].tokens.bbUSDT, - maxTradeVolume: fp('1e6').toString(), // $7.5m vault + maxTradeVolume: fp('0.5e6').toString(), // $7.5m vault oracleTimeout: USDT_ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: USDT_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts b/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts index b85431973..f796086e9 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_steakpyusd.ts @@ -60,7 +60,7 @@ async function main() { chainlinkFeed: PYUSD_USD_FEED, oracleError: PYUSD_ORACLE_ERROR.toString(), erc20: networkConfig[chainId].tokens.steakPYUSD, - maxTradeVolume: fp('0.5e6').toString(), // $1.7m vault + maxTradeVolume: fp('0.25e6').toString(), // $1.7m vault oracleTimeout: PYUSD_ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: PYUSD_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule