Skip to content

Commit f3655e9

Browse files
committed
add: real liquidation scenario; fix usd collateral fee split; liquidator keeps 40% of remaining collateral seized after repay amoutn
1 parent c349f5d commit f3655e9

File tree

6 files changed

+569
-61
lines changed

6 files changed

+569
-61
lines changed

proposals/ChainlinkOracleConfigs.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ abstract contract ChainlinkOracleConfigs is Test {
4444
_oracleConfigs[BASE_CHAIN_ID].push(
4545
OracleConfig("CHAINLINK_VIRTUAL_USD", "VIRTUAL")
4646
);
47+
_oracleConfigs[BASE_CHAIN_ID].push(
48+
OracleConfig("CHAINLINK_AERO_ORACLE", "AERO")
49+
);
50+
_oracleConfigs[BASE_CHAIN_ID].push(
51+
OracleConfig("cbETHETH_ORACLE", "cbETH")
52+
);
4753

4854
/// Initialize oracle configurations for Optimism
4955
_oracleConfigs[OPTIMISM_CHAIN_ID].push(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ contract mipx37 is HybridProposal, ChainlinkOracleConfigs, Networks {
2727
string public constant MORPHO_IMPLEMENTATION_NAME =
2828
"CHAINLINK_OEV_MORPHO_WRAPPER_IMPL";
2929

30-
uint16 public constant FEE_MULTIPLIER = 9900;
30+
uint16 public constant FEE_MULTIPLIER = 4000; // liquidator keeps 40% of the remaining collateral seized after repay amount
3131
uint256 public constant MAX_ROUND_DELAY = 10;
3232
uint256 public constant MAX_DECREMENTS = 10;
3333

src/oracles/ChainlinkOEVMorphoWrapper.sol

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ contract ChainlinkOEVMorphoWrapper is
2323
/// @notice The maximum basis points for the fee multiplier
2424
uint16 public constant MAX_BPS = 10000;
2525

26+
/// @notice Price mantissa decimals (used by ChainlinkOracle)
27+
uint8 private constant PRICE_MANTISSA_DECIMALS = 18;
28+
2629
/// @notice The ChainlinkOracle contract
2730
IChainlinkOracle public chainlinkOracle;
2831

@@ -556,32 +559,34 @@ contract ChainlinkOEVMorphoWrapper is
556559
uint256 collateralReceived,
557560
MarketParams memory marketParams
558561
) internal view returns (uint256 liquidatorFee, uint256 protocolFee) {
559-
// Get the loan token price from ChainlinkOracle
560562
uint256 loanTokenPrice = _getLoanTokenPrice(
561563
EIP20Interface(marketParams.loanToken)
562564
);
563-
564-
// Get the fully adjusted collateral token price
565565
uint256 collateralTokenPrice = _getCollateralTokenPrice(
566566
collateralAnswer,
567567
EIP20Interface(marketParams.collateralToken)
568568
);
569569

570-
// Calculate USD value of the repay amount
571-
uint256 repayValueUSD = (repayAmount * loanTokenPrice);
572-
uint256 collateralValueUSD = (collateralReceived *
573-
collateralTokenPrice);
570+
uint256 usdNormalizer = 10 ** PRICE_MANTISSA_DECIMALS; // 1e18
571+
uint256 repayUSD = (repayAmount * loanTokenPrice) / usdNormalizer;
572+
uint256 collateralUSD = (collateralReceived * collateralTokenPrice) /
573+
usdNormalizer;
574+
575+
// If collateral is worth less than repayment, liquidator gets all collateral
576+
if (collateralUSD <= repayUSD) {
577+
liquidatorFee = collateralReceived;
578+
protocolFee = 0;
579+
return (liquidatorFee, protocolFee);
580+
}
574581

575-
// Liquidator receives: collateral worth repay amount + bonus (remainder * feeMultiplier)
576-
uint256 liquidatorPaymentUSD = repayValueUSD +
577-
((collateralValueUSD - repayValueUSD) * uint256(feeMultiplier)) /
582+
// Liquidator gets the repayment amount + bonus (remainder * feeMultiplier)
583+
uint256 liquidatorUSD = repayUSD +
584+
((collateralUSD - repayUSD) * uint256(feeMultiplier)) /
578585
MAX_BPS;
579586

580-
// Convert USD value back to collateral token amount
581-
// Both prices from oracle are already scaled for token decimals, so simple division works
582-
liquidatorFee = liquidatorPaymentUSD / collateralTokenPrice;
587+
// Convert back to collateral token amount
588+
liquidatorFee = (liquidatorUSD * usdNormalizer) / collateralTokenPrice;
583589

584-
// Protocol gets the remainder
585590
protocolFee = collateralReceived - liquidatorFee;
586591
}
587592
}

src/oracles/ChainlinkOEVWrapper.sol

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import {MToken} from "../MToken.sol";
99
import {EIP20Interface} from "../EIP20Interface.sol";
1010
import {IChainlinkOracle} from "../interfaces/IChainlinkOracle.sol";
1111

12-
import {console} from "forge-std/console.sol";
13-
1412
/**
1513
* @title ChainlinkOEVWrapper
1614
* @notice A wrapper for Chainlink price feeds that allows early updates for liquidation
@@ -20,6 +18,12 @@ contract ChainlinkOEVWrapper is Ownable, AggregatorV3Interface {
2018
/// @notice The maximum basis points for the fee multiplier
2119
uint16 public constant MAX_BPS = 10000;
2220

21+
/// @notice Chainlink feed decimals (USD feeds use 8 decimals)
22+
uint8 private constant CHAINLINK_FEED_DECIMALS = 8;
23+
24+
/// @notice Price mantissa decimals (used by ChainlinkOracle)
25+
uint8 private constant PRICE_MANTISSA_DECIMALS = 18;
26+
2327
/// @notice The ChainlinkOracle contract
2428
IChainlinkOracle public immutable chainlinkOracle;
2529

@@ -368,7 +372,7 @@ contract ChainlinkOEVWrapper is Ownable, AggregatorV3Interface {
368372
}
369373

370374
// execute liquidation and redeem collateral
371-
uint256 collateralReceived = _executeLiquidationAndRedeem(
375+
uint256 collateralSeized = _executeLiquidationAndRedeem(
372376
borrower,
373377
repayAmount,
374378
mTokenCollateral,
@@ -377,16 +381,14 @@ contract ChainlinkOEVWrapper is Ownable, AggregatorV3Interface {
377381
underlyingCollateral
378382
);
379383

380-
console.log("collateralReceived", collateralReceived);
381-
382384
// Calculate the split of collateral between liquidator and protocol
383385
(
384386
uint256 liquidatorFee,
385387
uint256 protocolFee
386388
) = _calculateCollateralSplit(
387389
repayAmount,
388390
collateralAnswer,
389-
collateralReceived,
391+
collateralSeized,
390392
mTokenLoan,
391393
underlyingCollateral
392394
);
@@ -441,7 +443,6 @@ contract ChainlinkOEVWrapper is Ownable, AggregatorV3Interface {
441443
(10 ** decimalDelta);
442444
}
443445

444-
// Adjust for token decimals (same logic as ChainlinkOracle)
445446
uint256 collateralDecimalDelta = uint256(18) -
446447
uint256(underlyingCollateral.decimals());
447448
if (collateralDecimalDelta > 0) {
@@ -457,24 +458,22 @@ contract ChainlinkOEVWrapper is Ownable, AggregatorV3Interface {
457458
/// @param mTokenLoan The mToken market for the loan token
458459
/// @param underlyingLoan The underlying loan token interface
459460
/// @param underlyingCollateral The underlying collateral token interface
460-
/// @return collateralReceived The amount of underlying collateral received
461+
/// @return collateralSeized The amount of underlying collateral received
461462
function _executeLiquidationAndRedeem(
462463
address borrower,
463464
uint256 repayAmount,
464465
address mTokenCollateral,
465466
address mTokenLoan,
466467
EIP20Interface underlyingLoan,
467468
EIP20Interface underlyingCollateral
468-
) internal returns (uint256 collateralReceived) {
469+
) internal returns (uint256 collateralSeized) {
469470
uint256 collateralBefore = underlyingCollateral.balanceOf(
470471
address(this)
471472
);
472473
uint256 nativeBalanceBefore = address(this).balance;
473474

474-
// approve the mToken loan market to spend the loan tokens for liquidation
475475
underlyingLoan.approve(mTokenLoan, repayAmount);
476476

477-
// liquidate the borrower's position: repay their loan and seize their collateral
478477
uint256 mTokenCollateralBalanceBefore = MTokenInterface(
479478
mTokenCollateral
480479
).balanceOf(address(this));
@@ -487,7 +486,6 @@ contract ChainlinkOEVWrapper is Ownable, AggregatorV3Interface {
487486
"ChainlinkOEVWrapper: liquidation failed"
488487
);
489488

490-
// get the amount of mToken collateral received from liquidation
491489
uint256 mTokenBalanceDelta = MTokenInterface(mTokenCollateral)
492490
.balanceOf(address(this)) - mTokenCollateralBalanceBefore;
493491

@@ -507,56 +505,53 @@ contract ChainlinkOEVWrapper is Ownable, AggregatorV3Interface {
507505
require(success, "ChainlinkOEVWrapper: WETH deposit failed");
508506
}
509507

510-
console.log(
511-
"underlyingCollateral.balanceOf(address(this))",
512-
underlyingCollateral.balanceOf(address(this))
513-
);
514-
console.log("collateralBefore", collateralBefore);
515-
516-
collateralReceived =
508+
collateralSeized =
517509
underlyingCollateral.balanceOf(address(this)) -
518510
collateralBefore;
519511
}
520512

521513
/// @notice Calculate the split of seized collateral between liquidator and fee recipient
522514
/// @param repayAmount The amount of loan tokens being repaid
523-
/// @param collateralReceived The amount of collateral tokens seized
515+
/// @param collateralSeized The amount of collateral tokens seized
524516
/// @param mTokenLoan The mToken for the loan being repaid
525517
/// @param underlyingCollateral The underlying collateral token interface
526518
/// @return liquidatorFee The amount of collateral to send to the liquidator (repayment + bonus)
527519
/// @return protocolFee The amount of collateral to send to the fee recipient (remainder)
528520
function _calculateCollateralSplit(
529521
uint256 repayAmount,
530522
int256 collateralAnswer,
531-
uint256 collateralReceived,
523+
uint256 collateralSeized,
532524
address mTokenLoan,
533525
EIP20Interface underlyingCollateral
534526
) internal view returns (uint256 liquidatorFee, uint256 protocolFee) {
535-
uint256 loanTokenPrice = chainlinkOracle.getUnderlyingPrice(
527+
uint256 loanPrice = chainlinkOracle.getUnderlyingPrice(
536528
MToken(mTokenLoan)
537529
);
538-
539-
// Get the fully adjusted collateral token price
540-
uint256 collateralTokenPrice = _getCollateralTokenPrice(
530+
uint256 collateralPrice = _getCollateralTokenPrice(
541531
collateralAnswer,
542532
underlyingCollateral
543533
);
544534

545-
// Calculate USD value of the repay amount
546-
uint256 repayValueUSD = (repayAmount * loanTokenPrice);
547-
uint256 collateralValueUSD = (collateralReceived *
548-
collateralTokenPrice);
535+
uint256 usdNormalizer = 10 ** PRICE_MANTISSA_DECIMALS; // 1e18
536+
uint256 repayUSD = (repayAmount * loanPrice) / usdNormalizer;
537+
uint256 collateralUSD = (collateralSeized * collateralPrice) /
538+
usdNormalizer;
539+
540+
// If collateral is worth less than repayment, liquidator gets all collateral
541+
if (collateralUSD <= repayUSD) {
542+
liquidatorFee = collateralSeized;
543+
protocolFee = 0;
544+
return (liquidatorFee, protocolFee);
545+
}
549546

550-
// Liquidator receives: collateral worth repay amount + bonus (remainder * feeMultiplier)
551-
uint256 liquidatorPaymentUSD = repayValueUSD +
552-
((collateralValueUSD - repayValueUSD) * uint256(feeMultiplier)) /
547+
// Liquidator gets the repayment amount + bonus (remainder * feeMultiplier)
548+
uint256 liquidatorUSD = repayUSD +
549+
((collateralUSD - repayUSD) * uint256(feeMultiplier)) /
553550
MAX_BPS;
554551

555-
// Convert USD value back to collateral token amount
556-
// Both prices from oracle are already scaled for token decimals, so simple division works
557-
liquidatorFee = liquidatorPaymentUSD / collateralTokenPrice;
552+
// Convert back to collateral token amount
553+
liquidatorFee = (liquidatorUSD * usdNormalizer) / collateralPrice;
558554

559-
// Protocol gets the remainder
560-
protocolFee = collateralReceived - liquidatorFee;
555+
protocolFee = collateralSeized - liquidatorFee;
561556
}
562557
}

0 commit comments

Comments
 (0)