-
Notifications
You must be signed in to change notification settings - Fork 33
Add MIP-X36 #541
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
Merged
Merged
Add MIP-X36 #541
Changes from 4 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
634153a
Add MIP-X36
anajuliabit 5fdd975
send caps to 0.1
anajuliabit 1c36802
fix scientific notation
anajuliabit 5a2bb47
change actions description
anajuliabit 470b9f3
call set mint/borrow paused
anajuliabit 9408635
compile
anajuliabit d291241
update .md
anajuliabit de9bdcf
add deprecated addresses
anajuliabit 2678c0a
fix cross chain publish message it
anajuliabit b1f038c
fix compiler
anajuliabit c68c259
Merge branch 'mip-x34' into mip-x36
anajuliabit 9f5fdd3
deploy contracts
anajuliabit 7dce426
add proposal id
anajuliabit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 @@ | ||
| - Always set the id in @proposals/mips/mips.json to 0 when creating new proposals |
This file contains hidden or 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 hidden or 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 |
|---|---|---|
|
|
@@ -1223,5 +1223,10 @@ | |
| "addr": "0x74Cbb1E8B68dDD13B28684ECA202a351afD45EAa", | ||
| "isContract": true, | ||
| "name": "F-DEVGRANT" | ||
| }, | ||
| { | ||
| "addr": "0xe8dD07CCf5BC4922424140E44Eb970F5950725ef", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed, it matches the Chainlink docs. |
||
| "isContract": true, | ||
| "name": "CHAINLINK_wrsETH_ETH_EXCHANGE_RATE" | ||
| } | ||
| ] | ||
This file contains hidden or 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,303 @@ | ||
| //SPDX-License-Identifier: GPL-3.0-or-later | ||
| pragma solidity 0.8.19; | ||
|
|
||
| import "@forge-std/Test.sol"; | ||
|
|
||
| import {MToken} from "@protocol/MToken.sol"; | ||
| import {Comptroller} from "@protocol/Comptroller.sol"; | ||
| import {MErc20} from "@protocol/MErc20.sol"; | ||
| import {ChainlinkOracle} from "@protocol/oracles/ChainlinkOracle.sol"; | ||
| import {ChainlinkCompositeOracle} from "@protocol/oracles/ChainlinkCompositeOracle.sol"; | ||
| import {AggregatorV3Interface} from "@protocol/oracles/AggregatorV3Interface.sol"; | ||
|
|
||
| import {HybridProposal, ActionType} from "@proposals/proposalTypes/HybridProposal.sol"; | ||
| import {AllChainAddresses as Addresses} from "@proposals/Addresses.sol"; | ||
| import {MOONBEAM_FORK_ID, BASE_FORK_ID, OPTIMISM_FORK_ID} from "@utils/ChainIds.sol"; | ||
| import {ProposalActions} from "@proposals/utils/ProposalActions.sol"; | ||
| import {ChainIds} from "@utils/ChainIds.sol"; | ||
|
|
||
| /// @title MIP-X36: Deprecate wrsETH Markets with Exchange-Rate Oracle Transition | ||
| /// @author Moonwell Contributors | ||
| /// @notice Proposal to deprecate wrsETH markets on Base and Optimism by: | ||
| /// 1. Setting supply and borrow caps to 0 | ||
| /// 2. Deploying new ChainlinkCompositeOracle contracts using exchange rate feeds | ||
| /// 3. Updating oracle addresses for wrsETH markets | ||
| contract mipx36 is HybridProposal { | ||
| using ProposalActions for *; | ||
| using ChainIds for uint256; | ||
|
|
||
| string public constant override name = "MIP-X36"; | ||
|
|
||
| uint256 public constant NEW_SUPPLY_CAP = 0.1e18; | ||
| uint256 public constant NEW_BORROW_CAP = 0.1e18; | ||
|
|
||
| // Storage for deployed oracles | ||
| ChainlinkCompositeOracle public baseWrsethOracle; | ||
| ChainlinkCompositeOracle public optimismWrsethOracle; | ||
|
|
||
| constructor() { | ||
| bytes memory proposalDescription = abi.encodePacked( | ||
| vm.readFile("./proposals/mips/mip-x36/x36.md") | ||
| ); | ||
| _setProposalDescription(proposalDescription); | ||
| } | ||
|
|
||
| function run() public override { | ||
| primaryForkId().createForksAndSelect(); | ||
|
|
||
| Addresses addresses = new Addresses(); | ||
| vm.makePersistent(address(addresses)); | ||
|
|
||
| initProposal(addresses); | ||
|
|
||
| (, address deployerAddress, ) = vm.readCallers(); | ||
|
|
||
| if (DO_DEPLOY) deploy(addresses, deployerAddress); | ||
| if (DO_AFTER_DEPLOY) afterDeploy(addresses, deployerAddress); | ||
|
|
||
| if (DO_BUILD) build(addresses); | ||
| if (DO_RUN) run(addresses, deployerAddress); | ||
| if (DO_TEARDOWN) teardown(addresses, deployerAddress); | ||
| if (DO_VALIDATE) { | ||
| validate(addresses, deployerAddress); | ||
| console.log("Validation completed for proposal ", this.name()); | ||
| } | ||
| if (DO_PRINT) { | ||
| printProposalActionSteps(); | ||
|
|
||
| addresses.removeAllRestrictions(); | ||
| printCalldata(addresses); | ||
|
|
||
| _printAddressesChanges(addresses); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| function primaryForkId() public pure override returns (uint256) { | ||
| return BASE_FORK_ID; | ||
| } | ||
|
|
||
| function deploy(Addresses addresses, address) public override { | ||
| // Deploy new ChainlinkCompositeOracle for Base wrsETH | ||
| vm.selectFork(BASE_FORK_ID); | ||
| vm.startBroadcast(); | ||
|
|
||
| address baseEthUsdFeed = addresses.getAddress("CHAINLINK_ETH_USD"); | ||
| address baseWrsethEthExchangeRateFeed = addresses.getAddress("CHAINLINK_wrsETH_ETH_EXCHANGE_RATE"); | ||
|
|
||
| baseWrsethOracle = new ChainlinkCompositeOracle( | ||
| baseEthUsdFeed, | ||
| baseWrsethEthExchangeRateFeed, | ||
| address(0) | ||
| ); | ||
|
|
||
| vm.stopBroadcast(); | ||
|
|
||
| // Deploy new ChainlinkCompositeOracle for Optimism wrsETH | ||
| vm.selectFork(OPTIMISM_FORK_ID); | ||
| vm.startBroadcast(); | ||
|
|
||
| address optimismEthUsdFeed = addresses.getAddress("CHAINLINK_ETH_USD"); | ||
| address optimismWrsethEthExchangeRateFeed = addresses.getAddress("CHAINLINK_wrsETH_ETH_EXCHANGE_RATE"); | ||
|
|
||
| optimismWrsethOracle = new ChainlinkCompositeOracle( | ||
| optimismEthUsdFeed, | ||
| optimismWrsethEthExchangeRateFeed, | ||
| address(0) | ||
| ); | ||
|
|
||
| vm.stopBroadcast(); | ||
| } | ||
|
|
||
| function afterDeploy(Addresses addresses, address) public override { | ||
| vm.selectFork(BASE_FORK_ID); | ||
| addresses.changeAddress("CHAINLINK_wrsETH_COMPOSITE_ORACLE", address(baseWrsethOracle), true); | ||
|
|
||
| vm.selectFork(OPTIMISM_FORK_ID); | ||
| addresses.changeAddress("CHAINLINK_wrsETH_COMPOSITE_ORACLE", address(optimismWrsethOracle), true); | ||
| } | ||
|
|
||
| function build(Addresses addresses) public override { | ||
| // ============ BASE CHAIN ACTIONS ============ | ||
| vm.selectFork(BASE_FORK_ID); | ||
|
|
||
| address baseComptroller = addresses.getAddress("UNITROLLER"); | ||
| address baseWrsethMToken = addresses.getAddress("MOONWELL_wrsETH"); | ||
|
|
||
| address[] memory baseMarkets = new address[](1); | ||
| baseMarkets[0] = baseWrsethMToken; | ||
|
|
||
| uint256[] memory baseSupplyCaps = new uint256[](1); | ||
| baseSupplyCaps[0] = NEW_SUPPLY_CAP; | ||
|
|
||
| uint256[] memory baseBorrowCaps = new uint256[](1); | ||
| baseBorrowCaps[0] = NEW_BORROW_CAP; | ||
|
|
||
| // Set supply cap to 0 on Base | ||
| _pushAction( | ||
| baseComptroller, | ||
| abi.encodeWithSignature( | ||
| "_setMarketSupplyCaps(address[],uint256[])", | ||
| baseMarkets, | ||
| baseSupplyCaps | ||
| ), | ||
| "Set supply cap to 0.1e18 for wrsETH on Base", | ||
| ActionType.Base | ||
| ); | ||
|
|
||
| // Set borrow cap to 0 on Base | ||
| _pushAction( | ||
| baseComptroller, | ||
| abi.encodeWithSignature( | ||
| "_setMarketBorrowCaps(address[],uint256[])", | ||
| baseMarkets, | ||
| baseBorrowCaps | ||
| ), | ||
| "Set borrow cap to 0.1e18 for wrsETH on Base", | ||
| ActionType.Base | ||
| ); | ||
|
|
||
| // Update oracle price feed on Base | ||
| address baseChainlinkOracle = addresses.getAddress("CHAINLINK_ORACLE"); | ||
| _pushAction( | ||
| baseChainlinkOracle, | ||
| abi.encodeWithSignature( | ||
| "setFeed(string,address)", | ||
| "wrsETH", | ||
| address(baseWrsethOracle) | ||
| ), | ||
| "Update wrsETH oracle to exchange rate feed on Base", | ||
| ActionType.Base | ||
| ); | ||
|
|
||
| // ============ OPTIMISM CHAIN ACTIONS ============ | ||
| vm.selectFork(OPTIMISM_FORK_ID); | ||
|
|
||
| address optimismComptroller = addresses.getAddress("UNITROLLER"); | ||
| address optimismWrsethMToken = addresses.getAddress("MOONWELL_wrsETH"); | ||
|
|
||
| address[] memory optimismMarkets = new address[](1); | ||
| optimismMarkets[0] = optimismWrsethMToken; | ||
|
|
||
| uint256[] memory optimismSupplyCaps = new uint256[](1); | ||
| optimismSupplyCaps[0] = NEW_SUPPLY_CAP; | ||
|
|
||
| uint256[] memory optimismBorrowCaps = new uint256[](1); | ||
| optimismBorrowCaps[0] = NEW_BORROW_CAP; | ||
|
|
||
| // Set supply cap to 0 on Optimism | ||
| _pushAction( | ||
| optimismComptroller, | ||
| abi.encodeWithSignature( | ||
| "_setMarketSupplyCaps(address[],uint256[])", | ||
| optimismMarkets, | ||
| optimismSupplyCaps | ||
| ), | ||
| "Set supply cap to 0.1e18 for wrsETH on Optimism", | ||
| ActionType.Optimism | ||
| ); | ||
|
|
||
| // Set borrow cap to 0 on Optimism | ||
| _pushAction( | ||
| optimismComptroller, | ||
| abi.encodeWithSignature( | ||
| "_setMarketBorrowCaps(address[],uint256[])", | ||
| optimismMarkets, | ||
| optimismBorrowCaps | ||
| ), | ||
| "Set borrow cap to 0.1e18 for wrsETH on Optimism", | ||
| ActionType.Optimism | ||
| ); | ||
|
|
||
| // Update oracle price feed on Optimism | ||
| address optimismChainlinkOracle = addresses.getAddress("CHAINLINK_ORACLE"); | ||
| _pushAction( | ||
| optimismChainlinkOracle, | ||
| abi.encodeWithSignature( | ||
| "setFeed(string,address)", | ||
| "wrsETH", | ||
| address(optimismWrsethOracle) | ||
| ), | ||
| "Update wrsETH oracle to exchange rate feed on Optimism", | ||
| ActionType.Optimism | ||
| ); | ||
| } | ||
|
|
||
| function teardown(Addresses addresses, address) public pure override {} | ||
|
|
||
| function validate(Addresses addresses, address) public override { | ||
| // ============ VALIDATE BASE CHAIN ============ | ||
| vm.selectFork(BASE_FORK_ID); | ||
|
|
||
| Comptroller baseComptroller = Comptroller(addresses.getAddress("UNITROLLER")); | ||
| address baseWrsethMToken = addresses.getAddress("MOONWELL_wrsETH"); | ||
|
|
||
| // Validate supply cap is 0 | ||
| uint256 baseSupplyCap = baseComptroller.supplyCaps(baseWrsethMToken); | ||
| assertEq( | ||
| baseSupplyCap, | ||
| NEW_SUPPLY_CAP, | ||
| "Base wrsETH supply cap not set to 0.1e18" | ||
| ); | ||
|
|
||
| // Validate borrow cap is 0 | ||
| uint256 baseBorrowCap = baseComptroller.borrowCaps(baseWrsethMToken); | ||
| assertEq( | ||
| baseBorrowCap, | ||
| NEW_BORROW_CAP, | ||
| "Base wrsETH borrow cap not set to 0.1e18" | ||
| ); | ||
|
|
||
| // Validate oracle is updated | ||
| ChainlinkOracle baseChainlinkOracle = ChainlinkOracle( | ||
| addresses.getAddress("CHAINLINK_ORACLE") | ||
| ); | ||
| AggregatorV3Interface baseFeed = baseChainlinkOracle.getFeed("wrsETH"); | ||
| assertEq( | ||
| address(baseFeed), | ||
| address(baseWrsethOracle), | ||
| "Base wrsETH oracle not updated" | ||
| ); | ||
|
|
||
| // Validate price can be fetched | ||
| (, int256 basePrice, , , ) = baseFeed.latestRoundData(); | ||
| assertGt(uint256(basePrice), 0, "Base wrsETH price check failed"); | ||
|
|
||
| // ============ VALIDATE OPTIMISM CHAIN ============ | ||
| vm.selectFork(OPTIMISM_FORK_ID); | ||
|
|
||
| Comptroller optimismComptroller = Comptroller(addresses.getAddress("UNITROLLER")); | ||
| address optimismWrsethMToken = addresses.getAddress("MOONWELL_wrsETH"); | ||
|
|
||
| // Validate supply cap is 0 | ||
| uint256 optimismSupplyCap = optimismComptroller.supplyCaps(optimismWrsethMToken); | ||
| assertEq( | ||
| optimismSupplyCap, | ||
| NEW_SUPPLY_CAP, | ||
| "Optimism wrsETH supply cap not set to 0.1e18" | ||
| ); | ||
|
|
||
| // Validate borrow cap is 0 | ||
| uint256 optimismBorrowCap = optimismComptroller.borrowCaps(optimismWrsethMToken); | ||
| assertEq( | ||
| optimismBorrowCap, | ||
| NEW_BORROW_CAP, | ||
| "Optimism wrsETH borrow cap not set to 0.1e18" | ||
| ); | ||
|
|
||
| // Validate oracle is updated | ||
| ChainlinkOracle optimismChainlinkOracle = ChainlinkOracle( | ||
| addresses.getAddress("CHAINLINK_ORACLE") | ||
| ); | ||
| AggregatorV3Interface optimismFeed = optimismChainlinkOracle.getFeed("wrsETH"); | ||
| assertEq( | ||
| address(optimismFeed), | ||
| address(optimismWrsethOracle), | ||
| "Optimism wrsETH oracle not updated" | ||
| ); | ||
|
|
||
| // Validate price can be fetched | ||
| (, int256 optimismPrice, , , ) = optimismFeed.latestRoundData(); | ||
| assertGt(uint256(optimismPrice), 0, "Optimism wrsETH price check failed"); | ||
| } | ||
| } |
This file contains hidden or 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,59 @@ | ||
| ## MIP-X36: Disable Mint/Borrow in wrsETH Markets and Transition to Exchange-Rate Oracle | ||
|
|
||
| ### **Summary** | ||
|
|
||
| Following the oracle malfunction involving the wrsETH/ETH feed on November 4, 2025, this proposal seeks to formally disable minting and borrowing in the wrsETH markets on both Base and OP Mainnet and transition these markets to use an exchange-rate feed rather than a market price oracle. | ||
|
|
||
| These steps reduce further risk exposure and set the foundation for a gradual, orderly deprecation of the wrsETH markets. | ||
|
|
||
| ### **Background** | ||
|
|
||
| At approximately **5:44 AM UTC on November 4**, an oracle malfunction caused the **wrsETH/ETH feed** to report a faulty value, drastically overpricing wrsETH and enabling an attacker to borrow multiple assets using minimal collateral. | ||
|
|
||
| The wrsETH/USD price is derived by multiplying the Chainlink ETH/USD oracle with the wrsETH/ETH oracle. The wrsETH/ETH oracle erroneously reported **1 wrsETH = 1,649,934.6 ETH**, valuing each token at roughly **$5.8 billion**. This led to approximately **$3.7 million** in bad debt across the Moonwell protocol. | ||
|
|
||
| Immediately following the mispricing event: | ||
|
|
||
| * Supply and borrow caps for wrsETH were set to effectively zero. | ||
|
|
||
| * Borrow caps for all Core Markets on Base and Optimism were temporarily reduced to **0.1** to prevent additional over-borrowing. | ||
|
|
||
| * All deposits and withdrawals remained open, allowing suppliers to withdraw funds where liquidity was available. | ||
|
|
||
| ### **Proposal** | ||
|
|
||
| To maintain a risk-off stance and begin the safe wind-down of wrsETH exposure, this proposal will: | ||
|
|
||
| #### 1. Disable minting and borrowing in wrsETH markets on Base and OP Mainnet. | ||
|
|
||
| No new wrsETH can be supplied or borrowed. Supply and borrow caps will be set to a near-zero value (as setting caps to zero represents infinity at a contract level). Repayments and withdrawals will remain enabled for existing users. | ||
|
|
||
| #### 2. Transition to exchange-rate feeds, replacing the existing oracle. | ||
|
|
||
| This ensures accurate value representation based on wrsETH's exchange-rate mechanics rather than rely on market rate pricing. | ||
|
|
||
| The exchange-rate feeds can be found here: | ||
|
|
||
| **Base** | ||
| - [Chainlink Exchange-Rate Feed](https://data.chain.link/feeds/base/base/wrseth-eth-exchange-rate) | ||
| - [wrsETH Contract (BaseScan)](https://basescan.org/address/0xe8dD07CCf5BC4922424140E44Eb970F5950725ef) | ||
|
|
||
| **OP Mainnet** | ||
| - [Chainlink Exchange-Rate Feed](https://data.chain.link/feeds/optimism/mainnet/wrseth-eth-exchange-rate) | ||
| - [wrsETH Contract (Optimism Etherscan)](https://optimistic.etherscan.io/address/0x73b8BE3b653c5896BC34fC87cEBC8AcF4Fb7A545) | ||
|
|
||
| #### 3. **Prepare the markets for deprecation.** | ||
|
|
||
| Once minting and borrowing are disabled and the oracle is transitioned, the community can proceed with a gradual deprecation of wrsETH markets on Base and OP Mainnet. Users will receive advance notice and be encouraged to repay outstanding loans and withdraw wrsETH to avoid liquidation risk during the wind-down process. | ||
|
|
||
| ### **Rationale** | ||
|
|
||
| * Disabling mint and borrow eliminates the primary vectors for further exploitation. | ||
| * Transitioning to exchange-rate feeds ensures stable and accurate valuation until the market can be fully deprecated. | ||
| * The measured approach, risk-off first, gradual deprecation later, balances user protection with operational continuity. | ||
|
|
||
| ### **Voting Options** | ||
|
|
||
| Yes: Disable mint/borrow for wrsETH on Base and OP Mainnet and transition to exchange-rate feeds | ||
| No: Maintain the status quo. | ||
| Abstain: No preference |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verified it matches the chainlink docs.