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

Morpho eUSD Plugin (Base) #1214

Merged
merged 8 commits into from
Oct 30, 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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ jobs:
restore-keys: |
hardhat-network-fork-${{ runner.os }}-
hardhat-network-fork-
- run: yarn hardhat test ./test/plugins/individual-collateral/{cbeth,aave-v3,compoundv3,stargate,lido}/*.test.ts
- run: yarn hardhat test ./test/plugins/individual-collateral/{cbeth,aave-v3,compoundv3,stargate,lido,meta-morpho}/*.test.ts
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
Expand Down
4 changes: 4 additions & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export interface ITokens {
bbUSDT?: string
steakPYUSD?: string
Re7WETH?: string
meUSD?: string

pxETH?: string
apxETH?: string
Expand Down Expand Up @@ -514,6 +515,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca',
wstETH: '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452',
STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df',
eUSD: '0xCfA3Ef56d303AE4fAabA0592388F19d7C3399FB4',
meUSD: '0xbb819D845b573B5D7C538F5b85057160cfb5f313',
},
chainlinkFeeds: {
DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr
Expand All @@ -530,6 +533,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h
ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min
wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h
eUSD: '0x9b2C948dbA5952A1f5Ab6fA16101c1392b8da1ab', // 0.5%, 24h
},
GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock
COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol";
* Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA}
*
* For example: steakUSDC, steakPYUSD, bbUSDT
*
* Rewards need to be claimed manually, from off-chain. This can be done permissionlessly,
* by anyone, on behalf of the RToken's Backing Manager address.
* For more information: https://docs.morpho.org/rewards/tutorials/claim-rewards/
*
*/
contract MetaMorphoFiatCollateral is ERC4626FiatCollateral {
/// config.erc20 must be a MetaMorpho ERC4626 vault
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { ERC4626FiatCollateral } from "../ERC4626FiatCollateral.sol";
* Expected: {tok} != {ref}, {ref} == {target}, {target} != {UoA}
*
* For example: Re7WETH
*
* Rewards will need to be claimed manually, off-chain. This can be done permissionlessly by anyone,
* on behalf of the RToken's Backing Manager address.
* For more information: https://docs.morpho.org/rewards/tutorials/claim-rewards/
*
*/
contract MetaMorphoSelfReferentialCollateral is ERC4626FiatCollateral {
using FixLib for uint192;
Expand Down
18 changes: 18 additions & 0 deletions contracts/plugins/assets/meta-morpho/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ Morpho Blue is a permisionless lending protocol. At the time of this writing (Ma

MetaMorpho suffers from a similar to that of the Curve volatile pools which can lose assets on admin fee claim.

## Reward claiming

Rewards can be claimed permissionlessly by anyone from off-chain, following this detailed guide from the Morpho docs:

https://docs.morpho.org/rewards/tutorials/claim-rewards/

It requires the following steps:

1. Querying the Morpho API with the holder address: https://rewards.morpho.org/v1/users/0xADDRESS/distributions
2. Retrieving the distributor contract and sending a transaction to `claim()` the rewards with the following parameters (all obtained from the previous call):
- `account`: the holder address
- `reward`: the address of the reward token
- `claimable`: the amount of reward tokens to claim
- `proof`: the merkle proof

It is important to note that in the case of Rtokens, rewards will need to be claimed on behalf of the Backing Manager.

## Target tokens

**USD**
Expand All @@ -14,6 +31,7 @@ MetaMorpho suffers from a similar to that of the Curve volatile pools which can
| Steakhouse USDC | steakUSDC| 0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB | wstETH, MORPHO |
| Steakhouse PYSUD | steakPYUSD | 0xbEEF02e5E13584ab96848af90261f0C8Ee04722a | MORPHO |
| Flagship USDT | bbUSDT| 0x2C25f6C25770fFEC5959D34B94Bf898865e5D6b1 | MORPHO |
| Morpho eUSD (Base) | meUSD | 0xbb819D845b573B5D7C538F5b85057160cfb5f313 | MORPHO |

**ETH**

Expand Down
8 changes: 5 additions & 3 deletions scripts/addresses/8453-tmp-assets-collateral.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"cbETH": "0x851B461a9744f4c9E996C03072cAB6f44Fa04d0D",
"saBasUSDC": "0xC19f5d60e2Aca1174f3D5Fe189f0A69afaB76f50",
"cUSDCv3": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461",
"wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73"
"wstETH": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73",
"meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3"
},
"erc20s": {
"COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0",
Expand All @@ -23,6 +24,7 @@
"saBasUSDC": "0x6F6f81e5E66f503184f2202D83a79650c3285759",
"STG": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df",
"cUSDCv3": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA",
"wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452"
"wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
"meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313"
}
}
}
9 changes: 9 additions & 0 deletions scripts/addresses/base-4.0.0/8453-tmp-assets-collateral.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"assets": {},
"collateral": {
"meUSD": "0x0f1e10871e6a2D3A5Aa696b85b39d61a22A9e8C3"
},
"erc20s": {
"meUSD": "0xbb819D845b573B5D7C538F5b85057160cfb5f313"
}
}
1 change: 1 addition & 0 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ async function main() {
'phase2-assets/collaterals/deploy_aave_v3_usdc.ts',
'phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts',
'phase2-assets/collaterals/deploy_cbeth_collateral.ts',
'phase2-assets/collaterals/deploy_morphoeUSD.ts',
'phase2-assets/assets/deploy_stg.ts'
)
} else if (chainId == '42161' || chainId == '421614') {
Expand Down
94 changes: 94 additions & 0 deletions scripts/deployment/phase2-assets/collaterals/deploy_morphoeUSD.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import fs from 'fs'
import hre from 'hardhat'
import { getChainId } from '../../../../common/blockchain-utils'
import { baseL2Chains, 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 {
eUSD_ORACLE_TIMEOUT,
eUSD_ORACLE_ERROR,
eUSD_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 = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename)

const deployedCollateral: string[] = []

/******** Deploy MetaMorpho Morpho eUSD - meUSD **************************/

// Only for base
if (baseL2Chains.includes(hre.network.name)) {
const MetaMorphoFiatCollateralFactory: ContractFactory = await hre.ethers.getContractFactory(
'MetaMorphoFiatCollateral'
)

const collateral = <MetaMorphoFiatCollateral>await MetaMorphoFiatCollateralFactory.connect(
deployer
).deploy(
{
priceTimeout: PRICE_TIMEOUT.toString(),
chainlinkFeed: eUSD_USD_FEED,
oracleError: eUSD_ORACLE_ERROR.toString(),
erc20: networkConfig[chainId].tokens.meUSD,
maxTradeVolume: fp('1e6').toString(), // 17m vault
oracleTimeout: eUSD_ORACLE_TIMEOUT.toString(),
targetName: hre.ethers.utils.formatBytes32String('USD'),
defaultThreshold: eUSD_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule
pmckelvy1 marked this conversation as resolved.
Show resolved Hide resolved
delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(),
},
fp('1e-4') // can have mild drawdowns
pmckelvy1 marked this conversation as resolved.
Show resolved Hide resolved
)
await collateral.deployed()

console.log(`Deployed meUSD to ${hre.network.name} (${chainId}): ${collateral.address}`)
await (await collateral.refresh()).wait()
expect(await collateral.status()).to.equal(CollateralStatus.SOUND)

assetCollDeployments.collateral.meUSD = collateral.address
assetCollDeployments.erc20s.meUSD = networkConfig[chainId].tokens.meUSD
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
})
60 changes: 60 additions & 0 deletions scripts/verification/collateral-plugins/verify_morphoeUSD.ts
Original file line number Diff line number Diff line change
@@ -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 {
eUSD_ORACLE_TIMEOUT,
eUSD_ORACLE_ERROR,
eUSD_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 = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename)

/******** Verify meUSD **************************/
await verifyContract(
chainId,
deployments.collateral.meUSD,
[
{
priceTimeout: PRICE_TIMEOUT.toString(),
chainlinkFeed: eUSD_USD_FEED,
oracleError: eUSD_ORACLE_ERROR.toString(),
erc20: networkConfig[chainId].tokens.meUSD,
maxTradeVolume: fp('1e6').toString(),
oracleTimeout: eUSD_ORACLE_TIMEOUT.toString(),
targetName: hre.ethers.utils.formatBytes32String('USD'),
defaultThreshold: eUSD_ORACLE_ERROR.add(fp('0.01')).toString(), // +1% buffer rule
delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(),
},
fp('1e-4'), // can have small drawdowns
],
'contracts/plugins/assets/meta-morpho/MetaMorphoFiatCollateral.sol:MetaMorphoFiatCollateral'
)
}

main().catch((error) => {
console.error(error)
process.exitCode = 1
})
1 change: 1 addition & 0 deletions scripts/verify_etherscan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ async function main() {
'collateral-plugins/verify_aave_v3_usdc.ts',
'collateral-plugins/verify_wsteth.ts',
'collateral-plugins/verify_cbeth.ts',
'collateral-plugins/verify_morphoeUSD.ts',
'assets/verify_stg.ts'
)
} else if (chainId == '42161' || chainId == '421614') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { networkConfig } from '#/common/configuration'
import { bn, fp } from '#/common/numbers'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { MockV3Aggregator } from '@typechain/MockV3Aggregator'
Expand All @@ -15,19 +14,30 @@ import { MAX_UINT192 } from '#/common/constants'
import {
DELAY_UNTIL_DEFAULT,
FORK_BLOCK,
STEAKPYUSD,
STEAKUSDC,
BBUSDT,
PYUSD_USD_FEED,
PYUSD_ORACLE_ERROR,
PYUSD_ORACLE_TIMEOUT,
USDT_USD_FEED,
USDT_ORACLE_TIMEOUT,
USDT_ORACLE_ERROR,
USDC_USD_FEED,
USDC_ORACLE_TIMEOUT,
USDC_ORACLE_ERROR,
eUSD_USD_FEED,
eUSD_ORACLE_TIMEOUT,
eUSD_ORACLE_ERROR,
PRICE_TIMEOUT,
MEUSD,
} from './constants'
import { mintCollateralTo } from './mintCollateralTo'

interface MAFiatCollateralOpts extends CollateralOpts {
defaultPrice?: BigNumberish
defaultRefPerTok?: BigNumberish
forkNetwork?: string
}

const makeFiatCollateralTestSuite = (
Expand Down Expand Up @@ -167,6 +177,7 @@ const makeFiatCollateralTestSuite = (
itChecksNonZeroDefaultThreshold: it,
itHasRevenueHiding: it,
resetFork: getResetFork(FORK_BLOCK),
targetNetwork: defaultCollateralOpts.forkNetwork,
collateralName,
chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!,
itIsPricedByPeg: true,
Expand All @@ -180,7 +191,8 @@ const makeOpts = (
vault: string,
chainlinkFeed: string,
oracleTimeout: BigNumber,
oracleError: BigNumber
oracleError: BigNumber,
forkNetwork: string
): MAFiatCollateralOpts => {
return {
targetName: ethers.utils.formatBytes32String('USD'),
Expand All @@ -195,22 +207,28 @@ const makeOpts = (
defaultRefPerTok: fp('1'),
erc20: vault,
chainlinkFeed,
forkNetwork,
}
}

/*
Run the test suite
*/
const { tokens, chainlinkFeeds } = networkConfig[31337]

makeFiatCollateralTestSuite(
'MetaMorphoFiatCollateral - steakUSDC',
makeOpts(tokens.steakUSDC!, chainlinkFeeds.USDC!, USDC_ORACLE_TIMEOUT, USDC_ORACLE_ERROR)
makeOpts(STEAKUSDC, USDC_USD_FEED, USDC_ORACLE_TIMEOUT, USDC_ORACLE_ERROR, 'mainnet')
)
makeFiatCollateralTestSuite(
'MetaMorphoFiatCollateral - steakPYUSD',
makeOpts(tokens.steakPYUSD!, chainlinkFeeds.pyUSD!, PYUSD_ORACLE_TIMEOUT, PYUSD_ORACLE_ERROR)
makeOpts(STEAKPYUSD, PYUSD_USD_FEED, PYUSD_ORACLE_TIMEOUT, PYUSD_ORACLE_ERROR, 'mainnet')
)
makeFiatCollateralTestSuite(
'MetaMorphoFiatCollateral - bbUSDT',
makeOpts(tokens.bbUSDT!, chainlinkFeeds.USDT!, USDT_ORACLE_TIMEOUT, USDT_ORACLE_ERROR)
makeOpts(BBUSDT, USDT_USD_FEED, USDT_ORACLE_TIMEOUT, USDT_ORACLE_ERROR, 'mainnet')
)

makeFiatCollateralTestSuite(
'MetaMorphoFiatCollateral - meUSD',
makeOpts(MEUSD, eUSD_USD_FEED, eUSD_ORACLE_TIMEOUT, eUSD_ORACLE_ERROR, 'base')
)
Loading
Loading