Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meta morpho #1097

Merged
merged 7 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export interface ITokens {
sUSDT?: string
sETH?: string
MORPHO?: string
SWISE?: string
BTRFLY?: string
astETH?: string
wsgUSDC?: string
wsgUSDbC?: string
Expand All @@ -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 {
Expand Down Expand Up @@ -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',
Expand Down
41 changes: 41 additions & 0 deletions contracts/plugins/assets/ERC4626FiatCollateral.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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
tbrent marked this conversation as resolved.
Show resolved Hide resolved
*
* Warning: Only valid for linear ERC4626 vaults
*/
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);
}
}
23 changes: 23 additions & 0 deletions contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import { CollateralConfig } from "../AppreciatingFiatCollateral.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 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)
ERC4626FiatCollateral(config, revenueHiding)
{
require(config.defaultThreshold > 0, "defaultThreshold zero");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 { CollateralConfig } from "../AppreciatingFiatCollateral.sol";
import { FixLib, CEIL } from "../../../libraries/Fixed.sol";
import { OracleLib } from "../OracleLib.sol";
import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol";

/**
* @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 MetaMorphoSelfReferentialCollateral is ERC4626FiatCollateral {
using FixLib for uint192;
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)
ERC4626FiatCollateral(config, revenueHiding)
{

Check warning on line 27 in contracts/plugins/assets/meta-morpho/MetaMorphoSelfReferentialCollateral.sol

View workflow job for this annotation

GitHub Actions / Lint Checks

Code contains empty blocks
// require(config.defaultThreshold > 0, "defaultThreshold zero");
}

/// 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();
}
}
27 changes: 27 additions & 0 deletions contracts/plugins/assets/meta-morpho/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +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**.

## 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, 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.
47 changes: 47 additions & 0 deletions contracts/plugins/mocks/MockMetaMorpho4626.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading
Loading