Skip to content

Commit

Permalink
Usdm plugin and scripts (#1146)
Browse files Browse the repository at this point in the history
Co-authored-by: Taylor Brent <[email protected]>
  • Loading branch information
julianmrodri and tbrent authored Jun 26, 2024
1 parent de7b0fe commit 9598658
Show file tree
Hide file tree
Showing 16 changed files with 790 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ jobs:
restore-keys: |
hardhat-network-fork-${{ runner.os }}-
hardhat-network-fork-
- run: npx hardhat test ./test/plugins/individual-collateral/{aave-v3,compoundv3,curve/cvx}/*.test.ts
- run: npx hardhat test ./test/plugins/individual-collateral/{aave-v3,compoundv3,curve/cvx,mountain}/*.test.ts
env:
NODE_OPTIONS: '--max-old-space-size=8192'
TS_NODE_SKIP_IGNORE: true
Expand Down
7 changes: 7 additions & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export interface ITokens {
bbUSDT?: string
steakPYUSD?: string
Re7WETH?: string

// Mountain
USDM?: string
wUSDM?: string
}

export type ITokensKeys = Array<keyof ITokens>
Expand Down Expand Up @@ -541,6 +545,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
saArbUSDCn: '', // TODO our wrapper. remove from deployment script after placing here
aArbUSDT: '0x6ab707aca953edaefbc4fd23ba73294241490620',
saArbUSDT: '', // TODO our wrapper. remove from deployment script after placing here
USDM: '0x59d9356e565ab3a36dd77763fc0d87feaf85508c',
wUSDM: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812',
},
chainlinkFeeds: {
ARB: '0xb2A824043730FE05F3DA2efaFa1CBbe83fa548D6',
Expand All @@ -550,6 +556,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
USDT: '0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7',
RSR: '0xcfF9349ec6d027f20fC9360117fef4a1Ad38B488',
crvUSD: '0x0a32255dd4BB6177C994bAAc73E0606fDD568f66',
wUSDM: '0xdC6720c996Fad27256c7fd6E0a271e2A4687eF18',
},
GNOSIS_EASY_AUCTION: '0xcD033976a011F41D2AB6ef47984041568F818E73', // our deployment
COMET_REWARDS: '0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae',
Expand Down
31 changes: 31 additions & 0 deletions contracts/plugins/assets/mountain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Mountain USDM Collateral Plugin

## Summary

This plugin allows `wUSDM` holders to use their tokens as collateral in the Reserve Protocol.

`wUSDM` is an unowned, immutable, ERC4626-wrapper around the USDM token.

Since it is ERC4626, the redeemable USDM amount can be obtained by dividing `wUSDM.totalAssets()` by `wUSDM.totalSupply()`.

`USDM` contract: <https://etherscan.io/address/0x59d9356e565ab3a36dd77763fc0d87feaf85508c#code>

`wUSDM` contract: <https://etherscan.io/address/0x57f5e098cad7a3d1eed53991d4d66c45c9af7812#code>

## Oracles - Important!

A Chronicle oracle <https://chroniclelabs.org/> is available for `wUSDM`, Even though Chronicle oracles provide a compatible interface for reading prices, they require the reading contract to be **whitelisted** by Chronicle. It is important to provide the Chronicle team the collateral plugin address as soon as it is deployed to the network so they can whitelist it. This has to be done **before** the plugin is used by any RToken.

## Implementation

### Units

| tok | ref | target | UoA |
| ----- | ---- | ------ | --- |
| wUSDM | USDM | USD | USD |

### Functions

#### refPerTok {ref/tok}

`return shiftl_toFix(erc4626.convertToAssets(oneShare), -refDecimals)`
59 changes: 59 additions & 0 deletions contracts/plugins/assets/mountain/USDMCollateral.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "../../../libraries/Fixed.sol";
import "../ERC4626FiatCollateral.sol";

/**
* @title USDM Collateral
* @notice Collateral plugin for USDM (Mountain Protocol)
* tok = wUSDM (ERC4626 vault)
* ref = USDM
* tar = USD
* UoA = USD
*
* Note: Uses a Chronicle Oracle, which requires the plugin address to be whitelisted
*/

contract USDMCollateral is ERC4626FiatCollateral {
using OracleLib for AggregatorV3Interface;
using FixLib for uint192;

// solhint-disable no-empty-blocks

/// @param config.chainlinkFeed - {UoA/tok} - Chronicle oracle - Requires whitelisting!
constructor(CollateralConfig memory config, uint192 revenueHiding)
ERC4626FiatCollateral(config, revenueHiding)
{
require(config.defaultThreshold != 0, "defaultThreshold zero");
}

/// Can revert, used by other contract functions in order to catch errors
/// Should not return FIX_MAX for low
/// Should only return FIX_MAX for high if low is 0
/// @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
virtual
override
returns (
uint192 low,
uint192 high,
uint192 pegPrice
)
{
// {UoA/tok}
uint192 p = chainlinkFeed.price(oracleTimeout);
uint192 err = p.mul(oracleError, CEIL);

low = p - err;
high = p + err;
// assert(low <= high); obviously true just by inspection

// {target/ref} = {UoA/ref} = {UoA/tok} / {ref/tok}
pegPrice = p.div(underlyingRefPerTok());
}
}
59 changes: 59 additions & 0 deletions contracts/plugins/assets/mountain/vendor/IChronicle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// Toll (whitelist)
interface IToll {
/// @notice Thrown by protected function if caller not tolled.
/// @param caller The caller's address.
error NotTolled(address caller);

/// @notice Emitted when toll granted to address.
/// @param caller The caller's address.
/// @param who The address toll got granted to.
event TollGranted(address indexed caller, address indexed who);

/// @notice Grants address `who` toll.
/// @dev Only callable by auth'ed address.
/// @param who The address to grant toll.
function kiss(address who) external;

/// @notice Renounces address `who`'s toll.
/// @dev Only callable by auth'ed address.
/// @param who The address to renounce toll.
function diss(address who) external;

/// @notice Returns whether address `who` is tolled.
/// @param who The address to check.
/// @return True if `who` is tolled, false otherwise.
function tolled(address who) external view returns (bool);
}

/**
* @title IChronicle
*
* @notice Interface for Chronicle Protocol's oracle products
*/
interface IChronicle is IToll {
/// @notice Returns the oracle's current value.
/// @dev Reverts if no value set.
/// @return value The oracle's current value.
function read() external view returns (uint256 value);

/// @notice Returns the oracle's current value and its age.
/// @dev Reverts if no value set.
/// @return value The oracle's current value.
/// @return age The value's age.
function readWithAge() external view returns (uint256 value, uint256 age);

/// Chainlink compatibility
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
30 changes: 30 additions & 0 deletions scripts/addresses/42161-tmp-assets-collateral.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"assets": {
"COMP": "0x6882560A919714A742afd2A2a0af6b4D8d20cF22",
"ARB": "0x21fBa52dA03e1F964fa521532f8B8951fC212055"
},
"collateral": {
"DAI": "0x6FE56A3EEa3fEc93601a94D26bEa1876bD48192F",
"USDC": "0xa96aE05dFa869F4FCC4142E8D4E4F2706FEe2B57",
"USDT": "0x3Ac8F000D75a2EA4a9a36c6844410926bc0c32f7",
"saArbUSDCn": "0x7be9Bc50734820516693A376238Cc6Bf029BA682",
"saArbUSDT": "0x529D7e23Ce63efdcE41dA2a41296Fd7399157F5b",
"cUSDCv3": "0x8a5DfEa5cdA35AB374ac558951A3dF1437A6FcA6",
"cvxCrvUSDUSDT": "0xf729b03AcbD60c8aF9B449d51444445815a56d0e",
"cvxCrvUSDUSDC": "0x57547D29cf0D5B4d31c6c71Ec73b3A8c8416ade6",
"wUSDM": "0xA185a9fd314b61f33F740760a59cc46b31297e30"
},
"erc20s": {
"COMP": "0x354A6dA3fcde098F8389cad84b0182725c6C91dE",
"DAI": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
"USDC": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
"USDT": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9",
"saArbUSDCn": "0x030cDeCBDcA6A34e8De3f49d1798d5f70E3a3414",
"saArbUSDT": "0xffef97179f58a582dEf73e6d2e4BcD2BDC8ca128",
"cUSDCv3": "0xd54804250E9C561AEa9Dee34e9cf2342f767ACC5",
"ARB": "0x912ce59144191c1204e64559fe8253a0e49e6548",
"cvxCrvUSDUSDT": "0xf74d4C9b0F49fb70D8Ff6706ddF39e3a16D61E67",
"cvxCrvUSDUSDC": "0xBFEE9F3E015adC754066424AEd535313dc764116",
"wUSDM": "0x57f5e098cad7a3d1eed53991d4d66c45c9af7812"
}
}
38 changes: 38 additions & 0 deletions scripts/addresses/42161-tmp-deployments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"prerequisites": {
"RSR": "0xCa5Ca9083702c56b481D1eec86F1776FDbd2e594",
"RSR_FEED": "0xcfF9349ec6d027f20fC9360117fef4a1Ad38B488",
"GNOSIS_EASY_AUCTION": "0xcD033976a011F41D2AB6ef47984041568F818E73"
},
"tradingLib": "0x348644F24FA34c40a8E3C4Cf9aF14f8a96aD63fC",
"cvxMiningLib": "",
"facade": "0x387A0C36681A22F728ab54426356F4CAa6bB48a9",
"facets": {
"actFacet": "0xE774CCF1431c3DEe7Fa4c20f67534b61289CAa45",
"readFacet": "0x15175d35F3d88548B49600B4ee8067253A2e4e66"
},
"facadeWriteLib": "0x042D85e9eb1F4372ffA362240E0630229CaA1904",
"basketLib": "0x53f1Df4E5591Ae35Bf738742981669c3767241FA",
"facadeWrite": "0xe2B652E538543d02f985A5E422645A704633956d",
"deployer": "0xfd7eb6B208E1fa7B14E26A1fb10fFC17Cf695d68",
"rsrAsset": "0x7182e3A6E29936C8B14c4fa6f63a62D0b1D0f767",
"implementations": {
"main": "0xf7a9D27c3B60c78c6F6e2c2d6ED6E8B94b352461",
"trading": {
"gnosisTrade": "0xD42620d04fCe65B1F5E8Facc894a2e34D764FEc9",
"dutchTrade": "0x8b4374005291B8FCD14C4E947604b2FB3C660A73"
},
"components": {
"assetRegistry": "0xA9df960Af018178C0138CD5780c768A0a0A7e61f",
"backingManager": "0xD85Fac03804a3e44D29c494f3761D11A2262cBBe",
"basketHandler": "0x157b0C032192F5714BD68bf33dF96C122EA5e1d6",
"broker": "0xa24E0D3E77Ec4849A288C72F9d9bC4dF84B26558",
"distributor": "0x5Ef74A083Ac932b5f050bf41cDe1F67c659b4b88",
"furnace": "0x8A11D590B32186E1236B5E75F2d8D72c280dc880",
"rsrTrader": "0xaeCa35F0cB9d12D68adC4d734D4383593F109654",
"rTokenTrader": "0xaeCa35F0cB9d12D68adC4d734D4383593F109654",
"rToken": "0xC8f487B34251Eb76761168B70Dc10fA38B0Bd90b",
"stRSR": "0x437b525F96A2Da0A4b165efe27c61bea5c8d3CD4"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"saArbUSDT": "0x529D7e23Ce63efdcE41dA2a41296Fd7399157F5b",
"cUSDCv3": "0x8a5DfEa5cdA35AB374ac558951A3dF1437A6FcA6",
"cvxCrvUSDUSDT": "0xf729b03AcbD60c8aF9B449d51444445815a56d0e",
"cvxCrvUSDUSDC": "0x57547D29cf0D5B4d31c6c71Ec73b3A8c8416ade6"
"cvxCrvUSDUSDC": "0x57547D29cf0D5B4d31c6c71Ec73b3A8c8416ade6",
"wUSDM": "0xA185a9fd314b61f33F740760a59cc46b31297e30"
},
"erc20s": {
"COMP": "0x354A6dA3fcde098F8389cad84b0182725c6C91dE",
Expand All @@ -23,6 +24,7 @@
"cUSDCv3": "0xd54804250E9C561AEa9Dee34e9cf2342f767ACC5",
"ARB": "0x912ce59144191c1204e64559fe8253a0e49e6548",
"cvxCrvUSDUSDT": "0xf74d4C9b0F49fb70D8Ff6706ddF39e3a16D61E67",
"cvxCrvUSDUSDC": "0xBFEE9F3E015adC754066424AEd535313dc764116"
"cvxCrvUSDUSDC": "0xBFEE9F3E015adC754066424AEd535313dc764116",
"wUSDM": "0x57f5e098cad7a3d1eed53991d4d66c45c9af7812"
}
}
}
1 change: 1 addition & 0 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ async function main() {
'phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts',
'phase2-assets/collaterals/deploy_convex_crvusd_usdc_collateral.ts',
'phase2-assets/collaterals/deploy_convex_crvusd_usdt_collateral.ts',
'phase2-assets/collaterals/deploy_usdm.ts',
'phase2-assets/assets/deploy_arb.ts'
)
}
Expand Down
106 changes: 106 additions & 0 deletions scripts/deployment/phase2-assets/collaterals/deploy_usdm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import fs from 'fs'
import hre from 'hardhat'
import { getChainId } from '../../../../common/blockchain-utils'
import { arbitrumL2Chains, 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 { USDMCollateral } from '../../../../typechain'
import { ContractFactory } from 'ethers'
import {
DEFAULT_THRESHOLD,
DELAY_UNTIL_DEFAULT,
PRICE_TIMEOUT,
ORACLE_TIMEOUT,
ORACLE_ERROR,
} from '../../../../test/plugins/individual-collateral/mountain/constants'

async function main() {
// ==== Read Configuration ====
const [deployer] = await hre.ethers.getSigners()

const chainId = await getChainId(hre)

console.log(`Deploying Collateral to network ${hre.network.name} (${chainId})
with burner account: ${deployer.address}`)

if (!networkConfig[chainId]) {
throw new Error(`Missing network configuration for ${hre.network.name}`)
}

// Get phase1 deployment
const phase1File = getDeploymentFilename(chainId)
if (!fileExists(phase1File)) {
throw new Error(`${phase1File} doesn't exist yet. Run phase 1`)
}
// Check previous step completed
const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId)
const assetCollDeployments = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename)

const deployedCollateral: string[] = []

/******** Deploy USDM Collateral - wUSDM **************************/
let collateral: USDMCollateral

// Only on Arbitrum for now
if (arbitrumL2Chains.includes(hre.network.name)) {
const USDMCollateralFactory: ContractFactory = await hre.ethers.getContractFactory(
'USDMCollateral'
)

collateral = <USDMCollateral>await USDMCollateralFactory.connect(deployer).deploy(
{
priceTimeout: PRICE_TIMEOUT.toString(),
chainlinkFeed: networkConfig[chainId].chainlinkFeeds.wUSDM,
oracleError: ORACLE_ERROR.toString(), // 1%
erc20: networkConfig[chainId].tokens.wUSDM,
maxTradeVolume: fp('1e6').toString(), // $1m,
oracleTimeout: ORACLE_TIMEOUT.toString(), // 24 hr
targetName: hre.ethers.utils.formatBytes32String('USD'),
defaultThreshold: DEFAULT_THRESHOLD.toString(),
delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), // 24h
},
fp('1e-6')
)
} else {
throw new Error(`Unsupported chainId: ${chainId}`)
}

await collateral.deployed()

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

console.log(
'🚨 The wUSDM collateral requires chronicle to whitelist the collateral plugin after deployment 🚨'
)

console.log(
'🚨 After that, we need to return to this plugin and refresh() it and confirm it is SOUND 🚨'
)

assetCollDeployments.collateral.wUSDM = collateral.address
assetCollDeployments.erc20s.wUSDM = networkConfig[chainId].tokens.wUSDM
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
})
Loading

0 comments on commit 9598658

Please sign in to comment.