-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Deploy wstETH market on Mainnet (#911)
Co-authored-by: dmitriy-woof-software <[email protected]> Co-authored-by: GitHub Actions Bot <>
- Loading branch information
1 parent
b382d9e
commit 1d30582
Showing
15 changed files
with
870 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; | ||
} |
Oops, something went wrong.