Skip to content

Commit e688eb3

Browse files
committed
fix: mtokens must be whitelisted on redeemer contract
1 parent 73d1eec commit e688eb3

File tree

7 files changed

+613
-63
lines changed

7 files changed

+613
-63
lines changed

proposals/ChainlinkOracleConfigs.sol

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ abstract contract ChainlinkOracleConfigs is Test {
77
struct OracleConfig {
88
string oracleName; /// e.g., CHAINLINK_ETH_USD
99
string symbol; /// e.g., as found in addresses
10+
string mTokenKey; /// e.g., MOONWELL_WETH (defaults to MOONWELL_[symbol] if not specified)
1011
}
1112

1213
struct MorphoOracleConfig {
@@ -23,57 +24,60 @@ abstract contract ChainlinkOracleConfigs is Test {
2324
constructor() {
2425
/// Initialize oracle configurations for Base
2526
_oracleConfigs[BASE_CHAIN_ID].push(
26-
OracleConfig("CHAINLINK_ETH_USD", "WETH")
27+
OracleConfig("CHAINLINK_ETH_USD", "WETH", "MOONWELL_WETH")
2728
);
2829
_oracleConfigs[BASE_CHAIN_ID].push(
29-
OracleConfig("CHAINLINK_BTC_USD", "cbBTC")
30+
OracleConfig("CHAINLINK_BTC_USD", "cbBTC", "MOONWELL_cbBTC")
3031
);
3132
_oracleConfigs[BASE_CHAIN_ID].push(
32-
OracleConfig("CHAINLINK_EURC_USD", "EURC")
33+
OracleConfig("CHAINLINK_EURC_USD", "EURC", "MOONWELL_EURC")
3334
);
3435
_oracleConfigs[BASE_CHAIN_ID].push(
35-
OracleConfig("CHAINLINK_WELL_USD", "xWELL_PROXY")
36+
OracleConfig("CHAINLINK_WELL_USD", "xWELL_PROXY", "MOONWELL_WELL")
3637
);
3738
_oracleConfigs[BASE_CHAIN_ID].push(
38-
OracleConfig("CHAINLINK_USDS_USD", "USDS")
39+
OracleConfig("CHAINLINK_USDS_USD", "USDS", "MOONWELL_USDS")
3940
);
4041
_oracleConfigs[BASE_CHAIN_ID].push(
41-
OracleConfig("CHAINLINK_TBTC_USD", "TBTC")
42+
OracleConfig("CHAINLINK_TBTC_USD", "TBTC", "MOONWELL_TBTC")
4243
);
4344
_oracleConfigs[BASE_CHAIN_ID].push(
44-
OracleConfig("CHAINLINK_VIRTUAL_USD", "VIRTUAL")
45+
OracleConfig("CHAINLINK_VIRTUAL_USD", "VIRTUAL", "MOONWELL_VIRTUAL")
4546
);
4647
_oracleConfigs[BASE_CHAIN_ID].push(
47-
OracleConfig("CHAINLINK_AERO_ORACLE", "AERO")
48+
OracleConfig("CHAINLINK_AERO_ORACLE", "AERO", "MOONWELL_AERO")
4849
);
4950
_oracleConfigs[BASE_CHAIN_ID].push(
50-
OracleConfig("cbETHETH_ORACLE", "cbETH")
51+
OracleConfig("cbETHETH_ORACLE", "cbETH", "MOONWELL_cbETH")
52+
);
53+
_oracleConfigs[BASE_CHAIN_ID].push(
54+
OracleConfig("DAI_ORACLE", "DAI", "MOONWELL_DAI")
5155
);
5256

5357
/// Initialize oracle configurations for Optimism
5458
_oracleConfigs[OPTIMISM_CHAIN_ID].push(
55-
OracleConfig("CHAINLINK_ETH_USD", "WETH")
59+
OracleConfig("CHAINLINK_ETH_USD", "WETH", "MOONWELL_WETH")
5660
);
5761
_oracleConfigs[OPTIMISM_CHAIN_ID].push(
58-
OracleConfig("CHAINLINK_USDC_USD", "USDC")
62+
OracleConfig("CHAINLINK_USDC_USD", "USDC", "MOONWELL_USDC")
5963
);
6064
_oracleConfigs[OPTIMISM_CHAIN_ID].push(
61-
OracleConfig("CHAINLINK_DAI_USD", "DAI")
65+
OracleConfig("CHAINLINK_DAI_USD", "DAI", "MOONWELL_DAI")
6266
);
6367
_oracleConfigs[OPTIMISM_CHAIN_ID].push(
64-
OracleConfig("CHAINLINK_USDT_USD", "USDT")
68+
OracleConfig("CHAINLINK_USDT_USD", "USDT", "MOONWELL_USDT")
6569
);
6670
_oracleConfigs[OPTIMISM_CHAIN_ID].push(
67-
OracleConfig("CHAINLINK_WBTC_USD", "WBTC")
71+
OracleConfig("CHAINLINK_WBTC_USD", "WBTC", "MOONWELL_WBTC")
6872
);
6973
_oracleConfigs[OPTIMISM_CHAIN_ID].push(
70-
OracleConfig("CHAINLINK_OP_USD", "OP")
74+
OracleConfig("CHAINLINK_OP_USD", "OP", "MOONWELL_OP")
7175
);
7276
_oracleConfigs[OPTIMISM_CHAIN_ID].push(
73-
OracleConfig("CHAINLINK_VELO_USD", "VELO")
77+
OracleConfig("CHAINLINK_VELO_USD", "VELO", "MOONWELL_VELO")
7478
);
7579
_oracleConfigs[OPTIMISM_CHAIN_ID].push(
76-
OracleConfig("CHAINLINK_WELL_USD", "xWELL_PROXY")
80+
OracleConfig("CHAINLINK_WELL_USD", "xWELL_PROXY", "")
7781
);
7882

7983
/// Initialize Morpho market configurations for Base
@@ -84,7 +88,6 @@ abstract contract ChainlinkOracleConfigs is Test {
8488
MorphoOracleConfig("CHAINLINK_MAMO_USD", "CHAINLINK_MAMO_USD")
8589
);
8690

87-
/// NOTE: stkWELL does not have an equivalent MToken to add reserves to, so use TEMPORAL_GOVERNOR as the fee recipient
8891
_MorphoOracleConfigs[BASE_CHAIN_ID].push(
8992
MorphoOracleConfig("CHAINLINK_stkWELL_USD", "CHAINLINK_WELL_USD")
9093
);
@@ -102,7 +105,8 @@ abstract contract ChainlinkOracleConfigs is Test {
102105
for (uint256 i = 0; i < configLength; i++) {
103106
configs[i] = OracleConfig({
104107
oracleName: _oracleConfigs[chainId][i].oracleName,
105-
symbol: _oracleConfigs[chainId][i].symbol
108+
symbol: _oracleConfigs[chainId][i].symbol,
109+
mTokenKey: _oracleConfigs[chainId][i].mTokenKey
106110
});
107111
}
108112
}

proposals/mips/mip-x37/mip-x37.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,25 @@ contract mipx37 is HybridProposal, ChainlinkOracleConfigs, Networks {
180180
OEVProtocolFeeRedeemer feeRedeemer = new OEVProtocolFeeRedeemer(
181181
addresses.getAddress("MOONWELL_WETH")
182182
);
183+
184+
// Whitelist all mTokens
185+
OracleConfig[] memory oracleConfigs = getOracleConfigurations(
186+
block.chainid
187+
);
188+
for (uint256 i = 0; i < oracleConfigs.length; i++) {
189+
OracleConfig memory config = oracleConfigs[i];
190+
191+
// Only skip if mTokenKey is explicitly set to empty string
192+
if (bytes(config.mTokenKey).length == 0) {
193+
continue;
194+
}
195+
feeRedeemer.whitelistMarket(addresses.getAddress(config.mTokenKey));
196+
}
197+
198+
feeRedeemer.transferOwnership(
199+
addresses.getAddress("TEMPORAL_GOVERNOR")
200+
);
201+
183202
addresses.addAddress("OEV_PROTOCOL_FEE_REDEEMER", address(feeRedeemer));
184203
vm.stopBroadcast();
185204
}

src/OEVProtocolFeeRedeemer.sol

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// SPDX-License-Identifier: BSD-3-Clause
22
pragma solidity 0.8.19;
33

4-
import {MErc20Interface, MTokenInterface} from "./MTokenInterfaces.sol";
4+
import {MErc20Interface} from "./MTokenInterfaces.sol";
55
import {EIP20Interface} from "./EIP20Interface.sol";
6+
import {Ownable} from "@openzeppelin-contracts/contracts/access/Ownable.sol";
67

78
/**
89
* @title OEVProtocolFeeRedeemer
@@ -11,9 +12,19 @@ import {EIP20Interface} from "./EIP20Interface.sol";
1112
* @dev This contract receives fees from ChainlinkOEVWrapper and ChainlinkOEVMorphoWrapper. Handles mToken,
1213
* underlying token, and native ETH balances.
1314
*/
14-
contract OEVProtocolFeeRedeemer {
15+
contract OEVProtocolFeeRedeemer is Ownable {
1516
event ReservesAddedFromOEV(address indexed mToken, uint256 amount);
1617

18+
modifier onlyWhitelistedMarkets(address _market) {
19+
require(
20+
whitelistedMarkets[_market],
21+
"OEVProtocolFeeRedeemer: not whitelisted market"
22+
);
23+
_;
24+
}
25+
26+
mapping(address => bool) public whitelistedMarkets;
27+
1728
address public immutable MOONWELL_WETH;
1829

1930
/**
@@ -22,13 +33,25 @@ contract OEVProtocolFeeRedeemer {
2233
*/
2334
constructor(address _moonwellWETH) {
2435
MOONWELL_WETH = _moonwellWETH;
36+
whitelistedMarkets[_moonwellWETH] = true;
37+
38+
_transferOwnership(msg.sender);
39+
}
40+
41+
/**
42+
* @notice Allows the contract
43+
*/
44+
function whitelistMarket(address _market) external onlyOwner {
45+
whitelistedMarkets[_market] = true;
2546
}
2647

2748
/**
2849
* @notice Allows anyone to redeem this contract's mTokens and add the reserves to the mToken
2950
* @param _mToken Address of the mToken to redeem and add reserves to
3051
*/
31-
function redeemAndAddReserves(address _mToken) external {
52+
function redeemAndAddReserves(
53+
address _mToken
54+
) external onlyWhitelistedMarkets(_mToken) {
3255
(
3356
MErc20Interface mToken,
3457
EIP20Interface underlyingToken
@@ -61,7 +84,9 @@ contract OEVProtocolFeeRedeemer {
6184
* @notice Add reserves from underlying token balance
6285
* @param _mToken Address of the mToken to add reserves to
6386
*/
64-
function addReserves(address _mToken) external {
87+
function addReserves(
88+
address _mToken
89+
) external onlyWhitelistedMarkets(_mToken) {
6590
(
6691
MErc20Interface mToken,
6792
EIP20Interface underlyingToken
@@ -102,10 +127,6 @@ contract OEVProtocolFeeRedeemer {
102127
view
103128
returns (MErc20Interface mToken, EIP20Interface underlyingToken)
104129
{
105-
require(
106-
MTokenInterface(_mToken).isMToken(),
107-
"OEVProtocolFeeRedeemer: not an mToken"
108-
);
109130
mToken = MErc20Interface(_mToken);
110131
underlyingToken = EIP20Interface(mToken.underlying());
111132
}
@@ -142,4 +163,6 @@ contract OEVProtocolFeeRedeemer {
142163
mToken._addReserves(amount);
143164
emit ReservesAddedFromOEV(address(mToken), amount);
144165
}
166+
167+
receive() external payable {}
145168
}

test/integration/oracle/ChainlinkOEVMorphoWrapperIntegration.t.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ contract ChainlinkOEVMorphoWrapperIntegrationTest is
4444

4545
// Get redeemer contract from addresses
4646
redeemer = OEVProtocolFeeRedeemer(
47-
addresses.getAddress("OEV_PROTOCOL_FEE_REDEEMER")
47+
payable(addresses.getAddress("OEV_PROTOCOL_FEE_REDEEMER"))
4848
);
4949

5050
// Resolve morpho wrappers from shared morpho oracle configurations
@@ -337,11 +337,14 @@ contract ChainlinkOEVMorphoWrapperIntegrationTest is
337337
);
338338

339339
address mTokenCollateral = _findMTokenForUnderlying(collToken);
340-
if (mTokenCollateral != address(0)) {
340+
if (
341+
mTokenCollateral != address(0) &&
342+
redeemer.whitelistedMarkets(mTokenCollateral)
343+
) {
341344
_addReservesAndVerify(mTokenCollateral);
342345
} else {
343346
console2.log(
344-
"No mToken market found for collateral token",
347+
"No mToken market found or not whitelisted for collateral token",
345348
IERC20(collToken).symbol()
346349
);
347350
}

test/integration/oracle/ChainlinkOEVWrapperIntegration.t.sol

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ contract ChainlinkOEVWrapperIntegrationTest is
7878
vm.makePersistent(address(oracle));
7979

8080
redeemer = OEVProtocolFeeRedeemer(
81-
addresses.getAddress("OEV_PROTOCOL_FEE_REDEEMER")
81+
payable(addresses.getAddress("OEV_PROTOCOL_FEE_REDEEMER"))
8282
);
8383
vm.makePersistent(address(redeemer));
8484
}
@@ -588,7 +588,6 @@ contract ChainlinkOEVWrapperIntegrationTest is
588588
address borrower = _borrower(wrapper);
589589
address liquidator = _liquidator(wrapper);
590590
(, uint256 borrowAmount) = _setupSyntheticPosition(
591-
wrapper,
592591
mTokenCollateralAddr,
593592
mTokenBorrowAddr,
594593
borrower
@@ -605,6 +604,8 @@ contract ChainlinkOEVWrapperIntegrationTest is
605604
collateralToken: IERC20(
606605
addresses.getAddress(oracleConfigs[i].symbol)
607606
).symbol(),
607+
borrowMTokenKey: "MOONWELL_USDC",
608+
collateralMTokenKey: oracleConfigs[i].mTokenKey,
608609
borrower: borrower,
609610
liquidator: liquidator,
610611
repayAmount: borrowAmount / 10,
@@ -620,13 +621,11 @@ contract ChainlinkOEVWrapperIntegrationTest is
620621
/// @return collateralAmount The amount of collateral deposited
621622
/// @return borrowAmount The amount borrowed
622623
function _setupSyntheticPosition(
623-
ChainlinkOEVWrapper wrapper,
624624
address mTokenCollateralAddr,
625625
address mTokenBorrowAddr,
626626
address borrower
627627
) internal returns (uint256 collateralAmount, uint256 borrowAmount) {
628628
(collateralAmount, borrowAmount) = _calculateSyntheticAmounts(
629-
wrapper,
630629
mTokenCollateralAddr
631630
);
632631
_depositCollateral(
@@ -640,7 +639,6 @@ contract ChainlinkOEVWrapperIntegrationTest is
640639

641640
/// @notice Calculate collateral and borrow amounts for synthetic position
642641
function _calculateSyntheticAmounts(
643-
ChainlinkOEVWrapper wrapper,
644642
address mTokenCollateralAddr
645643
) internal view returns (uint256 collateralAmount, uint256 borrowAmount) {
646644
// Use oracle's getUnderlyingPrice which normalizes all prices to USD
@@ -1181,30 +1179,19 @@ contract ChainlinkOEVWrapperIntegrationTest is
11811179
ChainlinkOEVWrapper wrapper
11821180
)
11831181
{
1184-
// HACK: symbol in addresses not matching onchain token symbol
1185-
string memory collateralToken = (keccak256(
1186-
bytes(liquidation.collateralToken)
1187-
) == keccak256(bytes("tBTC")))
1188-
? "TBTC"
1189-
: liquidation.collateralToken;
1190-
string memory mTokenCollateralKey = string(
1191-
abi.encodePacked("MOONWELL_", collateralToken)
1192-
);
1193-
string memory mTokenBorrowKey = string(
1194-
abi.encodePacked("MOONWELL_", liquidation.borrowedToken)
1195-
);
1196-
11971182
require(
1198-
addresses.isAddressSet(mTokenCollateralKey),
1183+
addresses.isAddressSet(liquidation.collateralMTokenKey),
11991184
"Collateral mToken not found"
12001185
);
12011186
require(
1202-
addresses.isAddressSet(mTokenBorrowKey),
1187+
addresses.isAddressSet(liquidation.borrowMTokenKey),
12031188
"Borrow mToken not found"
12041189
);
12051190

1206-
mTokenCollateralAddr = addresses.getAddress(mTokenCollateralKey);
1207-
mTokenBorrowAddr = addresses.getAddress(mTokenBorrowKey);
1191+
mTokenCollateralAddr = addresses.getAddress(
1192+
liquidation.collateralMTokenKey
1193+
);
1194+
mTokenBorrowAddr = addresses.getAddress(liquidation.borrowMTokenKey);
12081195

12091196
bool found;
12101197
(wrapper, found) = _findWrapperForCollateral(
@@ -1445,17 +1432,18 @@ contract ChainlinkOEVWrapperIntegrationTest is
14451432
address mTokenCollateralAddr,
14461433
address mTokenBorrowAddr
14471434
) internal view {
1448-
PriceInfo memory priceInfo = _getPriceInfo(
1449-
mTokenCollateralAddr,
1450-
mTokenBorrowAddr
1451-
);
1452-
USDValues memory usdValues = _calculateUSDValues(
1453-
liquidation,
1454-
state,
1455-
priceInfo
1456-
);
1457-
1458-
_logLiquidationResults(liquidation, state, usdValues);
1435+
// NOTE: only needed to verify USD values on real liquidations
1436+
// PriceInfo memory priceInfo = _getPriceInfo(
1437+
// mTokenCollateralAddr,
1438+
// mTokenBorrowAddr
1439+
// );
1440+
// USDValues memory usdValues = _calculateUSDValues(
1441+
// liquidation,
1442+
// state,
1443+
// priceInfo
1444+
// );
1445+
1446+
// _logLiquidationResults(liquidation, state, usdValues);
14591447
_assertLiquidationResults(state);
14601448
}
14611449

0 commit comments

Comments
 (0)