Skip to content

Commit

Permalink
Deploy wstETH market on Mainnet (#911)
Browse files Browse the repository at this point in the history
Co-authored-by: dmitriy-woof-software <[email protected]>
Co-authored-by: GitHub Actions Bot <>
  • Loading branch information
MishaShWoof and dmitriy-woof-software authored Sep 17, 2024
1 parent b382d9e commit 1d30582
Show file tree
Hide file tree
Showing 15 changed files with 870 additions and 12 deletions.
20 changes: 18 additions & 2 deletions .github/workflows/enact-migration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ on:
description: Impersonate Account
required: false
default: ''
with_deploy:
type: boolean
description: Deploy Market
required: false
default: false
jobs:
enact-migration:
name: Enact Migration
Expand Down Expand Up @@ -108,6 +113,17 @@ jobs:
path: deployments/${{ github.event.inputs.network }}/${{ github.event.inputs.deployment }}/artifacts/
if: github.event.inputs.run_id != ''

- name: Run Deploy Market and Enact Migration (impersonate)
run: |
yarn hardhat deploy_and_migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }}
env:
DEBUG: true
ETH_PK: "${{ inputs.eth_pk }}"
NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8585"]')[github.event.inputs.eth_pk == ''] }}
GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }}
GOV_NETWORK: ${{ env.GOV_NETWORK }}
REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }}
if: github.event.inputs.impersonateAccount != '' && github.event.inputs.with_deploy == 'true'
- name: Run Enact Migration
run: |
yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }}
Expand All @@ -118,7 +134,7 @@ jobs:
GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }}
GOV_NETWORK: ${{ env.GOV_NETWORK }}
REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }}
if: github.event.inputs.impersonateAccount == ''
if: github.event.inputs.impersonateAccount == '' && github.event.inputs.with_deploy == 'false'
- name: Run Enact Migration (impersonate)
run: |
yarn hardhat migrate --network ${{ github.event.inputs.network }} --deployment ${{ github.event.inputs.deployment }} --enact --overwrite ${{ fromJSON('["", "--simulate"]')[github.event.inputs.simulate == 'true'] }} ${{ fromJSON('["", "--no-enacted"]')[github.event.inputs.no_enacted == 'true'] }} ${{ github.event.inputs.migration }} --impersonate ${{ github.event.inputs.impersonateAccount }}
Expand All @@ -129,7 +145,7 @@ jobs:
GOV_NETWORK_PROVIDER: ${{ fromJSON('["", "http://localhost:8685"]')[github.event.inputs.eth_pk == '' && env.GOV_NETWORK != ''] }}
GOV_NETWORK: ${{ env.GOV_NETWORK }}
REMOTE_ACCOUNTS: ${{ fromJSON('["", "true"]')[github.event.inputs.eth_pk == ''] }}
if: github.event.inputs.impersonateAccount != ''
if: github.event.inputs.impersonateAccount != '' && github.event.inputs.with_deploy == 'false'
- name: Commit changes
if: ${{ github.event.inputs.simulate == 'false' }}
run: |
Expand Down
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, 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, optimism-weth, scroll-goerli, scroll-usdc]
bases: [ development, mainnet, mainnet-weth, mainnet-usdt, mainnet-wsteth, 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, optimism-weth, scroll-goerli, scroll-usdc]
name: Run scenarios
env:
ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }}
Expand Down
109 changes: 109 additions & 0 deletions contracts/bulkers/MainnetBulkerWithWstETHSupport.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

import "./BaseBulker.sol";
import "../IWstETH.sol";

/**
* @title Compound's Bulker contract for Ethereum mainnet
* @notice Executes multiple Comet-related actions in a single transaction
* @author Compound
*/
contract MainnetBulkerWithWstETHSupport is BaseBulker {
/** General configuration constants **/

/// @notice The address of Lido staked ETH
address public immutable steth;

/// @notice The address of Lido wrapped staked ETH
address public immutable wsteth;

/** Actions **/

/// @notice The action for supplying staked ETH to Comet
bytes32 public constant ACTION_SUPPLY_STETH = "ACTION_SUPPLY_STETH";

/// @notice The action for withdrawing staked ETH from Comet
bytes32 public constant ACTION_WITHDRAW_STETH = "ACTION_WITHDRAW_STETH";

/** Custom errors **/

error UnsupportedBaseAsset();

/**
* @notice Construct a new MainnetBulker instance
* @param admin_ The admin of the Bulker contract
* @param weth_ The address of wrapped ETH
* @param wsteth_ The address of Lido wrapped staked ETH
**/
constructor(
address admin_,
address payable weth_,
address wsteth_
) BaseBulker(admin_, weth_) {
wsteth = wsteth_;
steth = IWstETH(wsteth_).stETH();
}

/**
* @notice Handles actions specific to the Ethereum mainnet version of Bulker, specifically supplying and withdrawing stETH
*/
function handleAction(bytes32 action, bytes calldata data) override internal {
if (action == ACTION_SUPPLY_STETH) {
(address comet, address to, uint stETHAmount) = abi.decode(data, (address, address, uint));
supplyStEthTo(comet, to, stETHAmount);
} else if (action == ACTION_WITHDRAW_STETH) {
(address comet, address to, uint wstETHAmount) = abi.decode(data, (address, address, uint));
withdrawStEthTo(comet, to, wstETHAmount);
} else {
revert UnhandledAction();
}
}

/**
* @notice Wraps stETH to wstETH and supplies to a user in Comet
* @dev Note: This contract must have permission to manage msg.sender's Comet account
* @dev Note: Supports `stETHAmount` of `uint256.max` to fully repay the wstETH debt
* @dev Note: Only for the cwstETHv3 market
*/
function supplyStEthTo(address comet, address to, uint stETHAmount) internal {
if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset();
uint256 _stETHAmount = stETHAmount == type(uint256).max
? IWstETH(wsteth).getStETHByWstETH(CometInterface(comet).borrowBalanceOf(msg.sender))
: stETHAmount;
doTransferIn(steth, msg.sender, _stETHAmount);
ERC20(steth).approve(wsteth, _stETHAmount);
uint wstETHAmount = IWstETH(wsteth).wrap(_stETHAmount);
ERC20(wsteth).approve(comet, wstETHAmount);
CometInterface(comet).supplyFrom(address(this), to, wsteth, wstETHAmount);
}

/**
* @notice Withdraws wstETH from Comet, unwraps it to stETH, and transfers it to a user
* @dev Note: This contract must have permission to manage msg.sender's Comet account
* @dev Note: Supports `amount` of `uint256.max` to withdraw all wstETH from Comet
* @dev Note: Only for the cwstETHv3 market
*/
function withdrawStEthTo(address comet, address to, uint stETHAmount) internal {
if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset();
uint wstETHAmount = stETHAmount == type(uint256).max
? CometInterface(comet).balanceOf(msg.sender)
: IWstETH(wsteth).getWstETHByStETH(stETHAmount);
CometInterface(comet).withdrawFrom(msg.sender, address(this), wsteth, wstETHAmount);
uint unwrappedStETHAmount = IWstETH(wsteth).unwrap(wstETHAmount);
doTransferOut(steth, to, unwrappedStETHAmount);
}

/**
* @notice Submits received ether to get stETH and wraps it to wstETH, received wstETH is transferred to Comet
*/
function deposit(address comet) external payable {
if(msg.sender != admin) revert Unauthorized();
if(CometInterface(comet).baseToken() != wsteth) revert UnsupportedBaseAsset();
(bool success, ) = payable(wsteth).call{value: msg.value}(new bytes(0));
if(!success) revert TransferOutFailed();

uint wstETHAmount = ERC20(wsteth).balanceOf(address(this));
doTransferOut(wsteth, comet, wstETHAmount);
}
}
46 changes: 46 additions & 0 deletions deployments/mainnet/wsteth/configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "Compound wstETH",
"symbol": "cWstETHv3",
"baseToken": "wstETH",
"baseTokenAddress": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",
"borrowMin": "0.1e18",
"governor": "0x6d903f6003cca6255d85cca4d3b5e5146dc33925",
"pauseGuardian": "0xbbf3f1421d886e9b2c5d716b5192ac998af2012c",
"storeFrontPriceFactor": 0.7,
"targetReserves": "5000e18",
"rates": {
"supplyBase": 0,
"supplySlopeLow": 0.012,
"supplyKink": 0.85,
"supplySlopeHigh": 1,
"borrowBase": 0.01,
"borrowSlopeLow": 0.014,
"borrowKink": 0.85,
"borrowSlopeHigh": 1.15
},
"tracking": {
"indexScale": "1e15",
"baseSupplySpeed": "92592592592e0",
"baseBorrowSpeed": "46296296296e0",
"baseMinForRewards": "10e18"
},
"rewardTokenAddress": "0xc00e94cb662c3520282e6f5717214004a7f26888",
"assets": {
"rsETH": {
"address": "0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7",
"decimals": "18",
"borrowCF": 0.88,
"liquidateCF": 0.91,
"liquidationFactor": 0.96,
"supplyCap": "10_000e18"
},
"ezETH": {
"address": "0xbf5495Efe5DB9ce00f80364C8B423567e58d2110",
"decimals": "18",
"borrowCF": 0.88,
"liquidateCF": 0.91,
"liquidationFactor": 0.94,
"supplyCap": "15_000e18"
}
}
}
77 changes: 77 additions & 0 deletions deployments/mainnet/wsteth/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Deployed, DeploymentManager } from '../../../plugins/deployment_manager';
import { DeploySpec, deployComet, exp } from '../../../src/deploy';

export default async function deploy(deploymentManager: DeploymentManager, deploySpec: DeploySpec): Promise<Deployed> {
const wstETH = await deploymentManager.existing('wstETH', '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0');
const weth = await deploymentManager.existing('weth', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2');
const rsETHToETHPriceFeed = await deploymentManager.fromDep('rsETH:priceFeed', 'mainnet', 'weth');
const wstETHToETHPriceFeed = await deploymentManager.fromDep('wstETH:priceFeed', 'mainnet', 'weth');
const ezETHToETHPriceFeed = await deploymentManager.fromDep('ezETH:priceFeed', 'mainnet', 'weth');
const weETHToETHPriceFeed = await deploymentManager.fromDep('weETH:priceFeed', 'mainnet', 'weth');

// Deploy constant price feed for wstETH
const wstETHConstantPriceFeed = await deploymentManager.deploy(
'wstETH:priceFeed',
'pricefeeds/ConstantPriceFeed.sol',
[
8, // decimals
exp(1, 8) // constantPrice
],
true
);

// Deploy reverse multiplicative price feed for rsETH
const rsETHScalingPriceFeed = await deploymentManager.deploy(
'rsETH:priceFeed',
'pricefeeds/ReverseMultiplicativePriceFeed.sol',
[
rsETHToETHPriceFeed.address, // rsETH / ETH price feed
wstETHToETHPriceFeed.address, // wstETH / ETH price feed (reversed)
8, // decimals
'rsETH / wstETH price feed' // description
],
true
);

// Deploy reverse multiplicative price feed for ezETH
const ezETHScalingPriceFeed = await deploymentManager.deploy(
'ezETH:priceFeed',
'pricefeeds/ReverseMultiplicativePriceFeed.sol',
[
ezETHToETHPriceFeed.address, // ezETH / ETH price feed
wstETHToETHPriceFeed.address, // wstETH / ETH price feed (reversed)
8, // decimals
'ezETH / wstETH price feed' // description
],
true
);

// Import shared contracts from cUSDCv3
const cometAdmin = await deploymentManager.fromDep('cometAdmin', 'mainnet', 'usdc');
const cometFactory = await deploymentManager.fromDep('cometFactory', 'mainnet', 'usdt');
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');

// Deploy all Comet-related contracts
const deployed = await deployComet(deploymentManager, deploySpec);
const { comet } = deployed;

// Deploy Bulker
const bulker = await deploymentManager.deploy(
'bulker',
'bulkers/MainnetBulkerWithWstETHSupport.sol',
[
await comet.governor(), // admin_
weth.address, // weth_
wstETH.address // wsteth_
],
true
);
console.log('Bulker deployed at:', bulker.address);

const bulkerNow = await deploymentManager.contract('bulker');
console.log('Bulker now at:', bulkerNow? bulkerNow.address: 'N/A');

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

0 comments on commit 1d30582

Please sign in to comment.