Skip to content

Commit

Permalink
Merge branch 'woof-software/add-ezETH-to-weth-mainnet' of github.com:…
Browse files Browse the repository at this point in the history
…woof-software/comet into woof-software/add-rsweth-to-mainnet-weth
  • Loading branch information
MishaShWoof committed Jul 11, 2024
2 parents 2ef7c83 + a22991a commit 36c789a
Show file tree
Hide file tree
Showing 25 changed files with 1,563 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-scenarios.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
strategy:
fail-fast: false
matrix:
bases: [ development, mainnet, mainnet-weth, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, scroll-goerli, scroll-usdc]
bases: [ development, mainnet, mainnet-weth, mainnet-usdt, goerli, goerli-weth, sepolia-usdc, sepolia-weth, fuji, mumbai, polygon, polygon-usdt, arbitrum-usdc.e, arbitrum-usdc, arbitrum-weth, arbitrum-usdt, arbitrum-goerli-usdc, arbitrum-goerli-usdc.e, base-usdbc, base-weth, base-usdc, base-goerli, base-goerli-weth, linea-goerli, optimism-usdc, optimism-usdt, scroll-goerli, scroll-usdc]
name: Run scenarios
env:
ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }}
Expand Down
98 changes: 98 additions & 0 deletions contracts/pricefeeds/EzETHExchangeRatePriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

import "../vendor/renzo/IBalancerRateProvider.sol";
import "../IPriceFeed.sol";

/**
* @title ezETH Scaling price feed
* @notice A custom price feed that scales up or down the price received from an underlying Renzo ezETH / ETH exchange rate price feed and returns the result
* @author Compound
*/
contract EzETHExchangeRatePriceFeed is IPriceFeed {
/** Custom errors **/
error InvalidInt256();
error BadDecimals();

/// @notice Version of the price feed
uint public constant VERSION = 1;

/// @notice Description of the price feed
string public description;

/// @notice Number of decimals for returned prices
uint8 public immutable override decimals;

/// @notice ezETH price feed where prices are fetched from
address public immutable underlyingPriceFeed;

/// @notice Whether or not the price should be upscaled
bool internal immutable shouldUpscale;

/// @notice The amount to upscale or downscale the price by
int256 internal immutable rescaleFactor;

/**
* @notice Construct a new ezETH scaling price feed
* @param ezETHRateProvider The address of the underlying price feed to fetch prices from
* @param decimals_ The number of decimals for the returned prices
**/
constructor(address ezETHRateProvider, uint8 decimals_, string memory description_) {
underlyingPriceFeed = ezETHRateProvider;
if (decimals_ > 18) revert BadDecimals();
decimals = decimals_;
description = description_;

uint8 ezETHRateProviderDecimals = 18;
// Note: Solidity does not allow setting immutables in if/else statements
shouldUpscale = ezETHRateProviderDecimals < decimals_ ? true : false;
rescaleFactor = (shouldUpscale
? signed256(10 ** (decimals_ - ezETHRateProviderDecimals))
: signed256(10 ** (ezETHRateProviderDecimals - decimals_))
);
}

/**
* @notice Price for the latest round
* @return roundId Round id from the underlying price feed
* @return answer Latest price for the asset in terms of ETH
* @return startedAt Timestamp when the round was started; passed on from underlying price feed
* @return updatedAt Timestamp when the round was last updated; passed on from underlying price feed
* @return answeredInRound Round id in which the answer was computed; passed on from underlying price feed
**/
function latestRoundData() override external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
uint256 rate = IBalancerRateProvider(underlyingPriceFeed).getRate();
// protocol uses only the answer value. Other data fields are not provided by the underlying pricefeed and are not used in Comet protocol
// https://etherscan.io/address/0x387dBc0fB00b26fb085aa658527D5BE98302c84C#readProxyContract
return (1, scalePrice(signed256(rate)), block.timestamp, block.timestamp, 1);
}

function signed256(uint256 n) internal pure returns (int256) {
if (n > uint256(type(int256).max)) revert InvalidInt256();
return int256(n);
}

function scalePrice(int256 price) internal view returns (int256) {
int256 scaledPrice;
if (shouldUpscale) {
scaledPrice = price * rescaleFactor;
} else {
scaledPrice = price / rescaleFactor;
}
return scaledPrice;
}

/**
* @notice Price for the latest round
* @return The version of the price feed contract
**/
function version() external pure returns (uint256) {
return VERSION;
}
}
95 changes: 95 additions & 0 deletions contracts/pricefeeds/ReverseMultiplicativePriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../IPriceFeed.sol";

/**
* @title Reverse multiplicative price feed
* @notice A custom price feed that multiplies the price from one price feed and the inverse price from another price feed and returns the result
* @dev for example if we need tokenX to eth, but there is only tokenX to usd, we can use this price feed to get tokenX to eth: tokenX to usd * reversed(eth to usd)
* @author Compound
*/
contract ReverseMultiplicativePriceFeed is IPriceFeed {
/** Custom errors **/
error BadDecimals();
error InvalidInt256();

/// @notice Version of the price feed
uint public constant VERSION = 1;

/// @notice Description of the price feed
string public override description;

/// @notice Number of decimals for returned prices
uint8 public immutable override decimals;

/// @notice Chainlink price feed A
address public immutable priceFeedA;

/// @notice Chainlink price feed B
address public immutable priceFeedB;

/// @notice Price feed A scale
int public immutable priceFeedAScale;

/// @notice Price feed B scale
int public immutable priceFeedBScale;

/// @notice Scale of this price feed
int public immutable priceFeedScale;

/**
* @notice Construct a new reverse multiplicative price feed
* @param priceFeedA_ The address of the first price feed to fetch prices from
* @param priceFeedB_ The address of the second price feed to fetch prices from that should be reversed
* @param decimals_ The number of decimals for the returned prices
* @param description_ The description of the price feed
**/
constructor(address priceFeedA_, address priceFeedB_, uint8 decimals_, string memory description_) {
priceFeedA = priceFeedA_;
priceFeedB = priceFeedB_;
uint8 priceFeedADecimals = AggregatorV3Interface(priceFeedA_).decimals();
uint8 priceFeedBDecimals = AggregatorV3Interface(priceFeedB_).decimals();
priceFeedAScale = signed256(10 ** (priceFeedADecimals));
priceFeedBScale = signed256(10 ** (priceFeedBDecimals));

if (decimals_ > 18) revert BadDecimals();
decimals = decimals_;
description = description_;
priceFeedScale = int256(10 ** decimals);
}

/**
* @notice Calculates the latest round data using data from the two price feeds
* @return roundId Round id from price feed B
* @return answer Latest price
* @return startedAt Timestamp when the round was started; passed on from price feed B
* @return updatedAt Timestamp when the round was last updated; passed on from price feed B
* @return answeredInRound Round id in which the answer was computed; passed on from price feed B
* @dev Note: Only the `answer` really matters for downstream contracts that use this price feed (e.g. Comet)
**/
function latestRoundData() override external view returns (uint80, int256, uint256, uint256, uint80) {
(, int256 priceA, , , ) = AggregatorV3Interface(priceFeedA).latestRoundData();
(uint80 roundId_, int256 priceB, uint256 startedAt_, uint256 updatedAt_, uint80 answeredInRound_) = AggregatorV3Interface(priceFeedB).latestRoundData();

if (priceA <= 0 || priceB <= 0) return (roundId_, 0, startedAt_, updatedAt_, answeredInRound_);

// int256 price = priceA * (priceFeedBScale/priceB) * priceFeedScale / priceFeedAScale;
int256 price = priceA * priceFeedBScale * priceFeedScale / priceB / priceFeedAScale;
return (roundId_, price, startedAt_, updatedAt_, answeredInRound_);
}

function signed256(uint256 n) internal pure returns (int256) {
if (n > uint256(type(int256).max)) revert InvalidInt256();
return int256(n);
}

/**
* @notice Price for the latest round
* @return The version of the price feed contract
**/
function version() external pure returns (uint256) {
return VERSION;
}
}
46 changes: 46 additions & 0 deletions contracts/test/MockOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

import "../vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

/**
* @title Mock oracle
* @notice Mock oracle to test the scaling price feed with updated update time
* @author Compound
*/
contract MockOracle {

/// @notice Number of decimals for returned prices
uint8 public immutable decimals;

/// @notice Underlying Chainlink price feed where prices are fetched from
address public immutable underlyingPriceFeed;

/**
* @notice Construct a new scaling price feed
* @param underlyingPriceFeed_ The address of the underlying price feed to fetch prices from
**/
constructor(address underlyingPriceFeed_) {
underlyingPriceFeed = underlyingPriceFeed_;
decimals = AggregatorV3Interface(underlyingPriceFeed_).decimals();
}

/**
* @notice Price for the latest round
* @return roundId Round id from the underlying price feed
* @return answer Latest price for the asset in terms of ETH
* @return startedAt Timestamp when the round was started; passed on from underlying price feed
* @return updatedAt Current timestamp
* @return answeredInRound Round id in which the answer was computed; passed on from underlying price feed
**/
function latestRoundData() external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
(uint80 roundId_, int256 price, uint256 startedAt_, , uint80 answeredInRound_) = AggregatorV3Interface(underlyingPriceFeed).latestRoundData();
return (roundId_, price, startedAt_, block.timestamp, answeredInRound_);
}
}
6 changes: 6 additions & 0 deletions contracts/vendor/renzo/IBalancerRateProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IBalancerRateProvider {
function getRate() external view returns (uint256);
}
83 changes: 83 additions & 0 deletions deployments/mainnet/usdt/configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"name": "Compound USDT",
"symbol": "cUSDTv3",
"baseToken": "USDT",
"baseTokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"borrowMin": "100e6",
"governor": "0x6d903f6003cca6255d85cca4d3b5e5146dc33925",
"pauseGuardian": "0xbbf3f1421d886e9b2c5d716b5192ac998af2012c",
"baseTokenPriceFeed": "0x3E7d1eAB13ad0104d2750B8863b489D65364e32D",
"storeFrontPriceFactor": 0.6,
"targetReserves": "20000000e6",
"rates": {
"supplyKink": 0.9,
"supplySlopeLow": 0.059,
"supplySlopeHigh": 2.9,
"supplyBase": 0,
"borrowKink": 0.9,
"borrowSlopeLow": 0.061,
"borrowSlopeHigh": 3.2,
"borrowBase": 0.015
},
"tracking": {
"indexScale": "1e15",
"baseSupplySpeed": "810185185185e0",
"baseBorrowSpeed": "578703703703e0",
"baseMinForRewards": "100000e6"
},
"rewardTokenAddress": "0xc00e94Cb662C3520282E6f5717214004A7f26888",
"assets": {
"COMP": {
"address": "0xc00e94Cb662C3520282E6f5717214004A7f26888",
"priceFeed": "0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5",
"decimals": "18",
"borrowCF": 0.65,
"liquidateCF": 0.70,
"liquidationFactor": 0.75,
"supplyCap": "100000e18"
},
"WETH": {
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"priceFeed": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
"decimals": "18",
"borrowCF": 0.83,
"liquidateCF": 0.9,
"liquidationFactor": 0.95,
"supplyCap": "500000e18"
},
"WBTC": {
"address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
"decimals": "8",
"borrowCF": 0.80,
"liquidateCF": 0.85,
"liquidationFactor": 0.95,
"supplyCap": "18000e8"
},
"UNI": {
"address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
"priceFeed": "0x553303d460EE0afB37EdFf9bE42922D8FF63220e",
"decimals": "18",
"borrowCF": 0.75,
"liquidateCF": 0.81,
"liquidationFactor": 0.85,
"supplyCap": "3000000e18"
},
"LINK": {
"address": "0x514910771af9ca656af840dff83e8264ecf986ca",
"priceFeed": "0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c",
"decimals": "18",
"borrowCF": 0.79,
"liquidateCF": 0.85,
"liquidationFactor": 0.93,
"supplyCap": "2000000e18"
},
"wstETH": {
"address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",
"decimals": "18",
"borrowCF": 0.80,
"liquidateCF": 0.85,
"liquidationFactor": 0.95,
"supplyCap": "9000e18"
}
}
}
54 changes: 54 additions & 0 deletions deployments/mainnet/usdt/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager';
import { DeploySpec, deployComet } from '../../../src/deploy';

export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise<Deployed> {
const USDT = await deploymentManager.existing('USDT', '0xdAC17F958D2ee523a2206206994597C13D831ec7');
const WBTC = await deploymentManager.existing('WBTC', '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599');
const WETH = await deploymentManager.existing('WETH', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2');
const COMP = await deploymentManager.existing('COMP', '0xc00e94Cb662C3520282E6f5717214004A7f26888');
const LINK = await deploymentManager.existing('LINK', '0x514910771af9ca656af840dff83e8264ecf986ca');
const UNI = await deploymentManager.existing('UNI', '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984');
const stETH = await deploymentManager.existing('stETH', '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84');
const wstETH = await deploymentManager.existing('wstETH', '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0');

const wbtcScalingPriceFeed = await deploymentManager.deploy(
'WBTC:priceFeed',
'pricefeeds/WBTCPriceFeed.sol',
[
'0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // WBTC / BTC price feed
'0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c', // BTC / USD price feed
8 // decimals
]
);

// WETH Mainnet makret uses custom price feed wstETH / ETH (WstETHPriceFeed.sol)
// We uses this already existing price feed on address https://etherscan.io/address/0x4F67e4d9BD67eFa28236013288737D39AeF48e79
// As we have wstETH / ETH, we just need ETH / USD to receive wstETH / USD price feed
const wstETHtoUsdPriceFeed = await deploymentManager.deploy(
'wstETH:priceFeed',
'pricefeeds/MultiplicativePriceFeed.sol',
[
'0x4F67e4d9BD67eFa28236013288737D39AeF48e79', // stETH / ETH price feed
'0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', // ETH / USD price feed
8,
"Custom price feed for wstETH / USD"
]
);

// Import shared contracts from cUSDCv3
const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'mainnet', 'usdc');
const $configuratorImpl = await deploymentManager.fromDep('configurator:implementation', 'mainnet', 'usdc');
const configurator = await deploymentManager.fromDep('configurator', 'mainnet', 'usdc');
const rewards = await deploymentManager.fromDep('rewards', 'mainnet', 'usdc');
const bulker = await deploymentManager.fromDep('bulker', 'mainnet', 'weth');

// Deploy Comet
const deployed = await deployComet(deploymentManager, deploySpec);

return {
...deployed,
bulker,
rewards,
COMP
};
}
Loading

0 comments on commit 36c789a

Please sign in to comment.