Skip to content
This repository was archived by the owner on Aug 26, 2024. It is now read-only.

Commit ca3add7

Browse files
added health factor based thresholding
1 parent dc8ad4c commit ca3add7

File tree

2 files changed

+86
-31
lines changed

2 files changed

+86
-31
lines changed

contracts/IonicLiquidator.sol

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { ICErc20 } from "./compound/CTokenInterfaces.sol";
2222
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelay.sol";
2323
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelayFeeReceiver.sol";
2424

25+
import "./PoolLens.sol";
26+
2527
/**
2628
* @title IonicLiquidator
2729
* @author David Lucid <[email protected]> (https://github.com/davidlucid)
@@ -70,10 +72,21 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee, I
7072
/**
7173
* @dev Addres of Pyth Express Relay for preventing value leakage in liquidations.
7274
*/
73-
IExpressRelay private expressRelay;
75+
IExpressRelay public expressRelay;
76+
/**
77+
* @dev Pool Lens.
78+
*/
79+
PoolLens public lens;
80+
/**
81+
* @dev Health Factor below which PER permissioning is bypassed.
82+
*/
83+
uint256 public healthFactorThreshold;
7484

75-
modifier onlyPERPermissioned(address borrower) {
76-
require(expressRelay.isPermissioned(address(this), abi.encode(borrower)), "invalid liquidation");
85+
modifier onlyPERPermissioned(address borrower, ICErc20 cToken) {
86+
uint256 currentHealthFactor = lens.getHealthFactor(borrower, cToken.comptroller());
87+
if (currentHealthFactor > healthFactorThreshold) {
88+
require(expressRelay.isPermissioned(address(this), abi.encode(borrower)), "invalid liquidation");
89+
}
7790
_;
7891
}
7992

@@ -121,7 +134,7 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee, I
121134
ICErc20 cTokenCollateral,
122135
uint256 minOutputAmount,
123136
bool redeemCollateral
124-
) external onlyPERPermissioned(borrower) returns (uint256) {
137+
) external onlyPERPermissioned(borrower, cTokenCollateral) returns (uint256) {
125138
// Transfer tokens in, approve to cErc20, and liquidate borrow
126139
require(repayAmount > 0, "Repay amount (transaction value) must be greater than 0.");
127140
IERC20Upgradeable underlying = IERC20Upgradeable(cErc20.underlying());
@@ -162,7 +175,7 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee, I
162175
*/
163176
function safeLiquidateToTokensWithFlashLoan(
164177
LiquidateToTokensWithFlashSwapVars calldata vars
165-
) external onlyPERPermissioned(vars.borrower) returns (uint256) {
178+
) external onlyPERPermissioned(vars.borrower, vars.cTokenCollateral) returns (uint256) {
166179
// Input validation
167180
require(vars.repayAmount > 0, "Repay amount must be greater than 0.");
168181

@@ -434,6 +447,15 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee, I
434447
expressRelay = IExpressRelay(_expressRelay);
435448
}
436449

450+
function setPoolLens(PoolLens _poolLens) external onlyOwner {
451+
lens = _poolLens;
452+
}
453+
454+
function setHealthFactorThreshold(uint256 _healthFactorThreshold) external onlyOwner {
455+
require(_healthFactorThreshold <= 1e18, "Invalid Health Factor Threshold");
456+
healthFactorThreshold = _healthFactorThreshold;
457+
}
458+
437459
/**
438460
* @dev Redeem "special" collateral tokens (before swapping the output for borrowed tokens to be repaid via Uniswap).
439461
* Public visibility because we have to call this function externally if called from a payable IonicLiquidator function (for some reason delegatecall fails when called with msg.value > 0).

contracts/IonicUniV3Liquidator.sol

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { IUniswapV3Quoter } from "./external/uniswap/quoter/interfaces/IUniswapV
1616

1717
import { ICErc20 } from "./compound/CTokenInterfaces.sol";
1818

19+
import "./PoolLens.sol";
20+
1921
/**
2022
* @title IonicUniV3Liquidator
2123
* @author Veliko Minkov <[email protected]> (https://github.com/vminkov)
@@ -48,6 +50,27 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
4850
mapping(address => bool) public redemptionStrategiesWhitelist;
4951
IUniswapV3Quoter public quoter;
5052

53+
/**
54+
* @dev Addres of Pyth Express Relay for preventing value leakage in liquidations.
55+
*/
56+
IExpressRelay public expressRelay;
57+
/**
58+
* @dev Pool Lens.
59+
*/
60+
PoolLens public lens;
61+
/**
62+
* @dev Health Factor below which PER permissioning is bypassed.
63+
*/
64+
uint256 public healthFactorThreshold;
65+
66+
modifier onlyPERPermissioned(address borrower, ICErc20 cToken) {
67+
uint256 currentHealthFactor = lens.getHealthFactor(borrower, cToken.comptroller());
68+
if (currentHealthFactor > healthFactorThreshold) {
69+
require(expressRelay.isPermissioned(address(this), abi.encode(borrower)), "invalid liquidation");
70+
}
71+
_;
72+
}
73+
5174
function initialize(address _wtoken, address _quoter) external initializer {
5275
__Ownable_init();
5376
W_NATIVE_ADDRESS = _wtoken;
@@ -69,15 +92,25 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
6992
ICErc20 cTokenCollateral,
7093
uint256 minOutputAmount,
7194
bool redeemCollateral
72-
) external returns (uint256) {
95+
) external onlyPERPermissioned(borrower, cTokenCollateral) returns (uint256) {
7396
// Transfer tokens in, approve to cErc20, and liquidate borrow
7497
require(repayAmount > 0, "Repay amount (transaction value) must be greater than 0.");
7598
IERC20Upgradeable underlying = IERC20Upgradeable(cErc20.underlying());
7699
underlying.safeTransferFrom(msg.sender, address(this), repayAmount);
77100
underlying.approve(address(cErc20), repayAmount);
78101
require(cErc20.liquidateBorrow(borrower, repayAmount, address(cTokenCollateral)) == 0, "Liquidation failed.");
79-
// Transfer seized amount to sender
80-
return transferSeizedFunds(address(cTokenCollateral), minOutputAmount);
102+
103+
if (redeemCollateral) {
104+
// Redeem seized cTokens for underlying asset
105+
uint256 seizedCTokenAmount = cTokenCollateral.balanceOf(address(this));
106+
require(seizedCTokenAmount > 0, "No cTokens seized.");
107+
uint256 redeemResult = cTokenCollateral.redeem(seizedCTokenAmount);
108+
require(redeemResult == 0, "Error calling redeeming seized cToken: error code not equal to 0");
109+
110+
return transferSeizedFunds(address(cTokenCollateral.underlying()), minOutputAmount);
111+
} else {
112+
return transferSeizedFunds(address(cTokenCollateral), minOutputAmount);
113+
}
81114
}
82115

83116
/**
@@ -94,10 +127,9 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
94127
return seizedOutputAmount;
95128
}
96129

97-
function safeLiquidateToTokensWithFlashLoan(LiquidateToTokensWithFlashSwapVars calldata vars)
98-
external
99-
returns (uint256)
100-
{
130+
function safeLiquidateToTokensWithFlashLoan(
131+
LiquidateToTokensWithFlashSwapVars calldata vars
132+
) external onlyPERPermissioned(borrower, cTokenCollateral) returns (uint256) {
101133
// Input validation
102134
require(vars.repayAmount > 0, "Repay amount must be greater than 0.");
103135

@@ -149,27 +181,15 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
149181
* @dev Callback function for Uniswap flashloans.
150182
*/
151183

152-
function supV3FlashCallback(
153-
uint256 fee0,
154-
uint256 fee1,
155-
bytes calldata data
156-
) external {
184+
function supV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
157185
uniswapV3FlashCallback(fee0, fee1, data);
158186
}
159187

160-
function algebraFlashCallback(
161-
uint256 fee0,
162-
uint256 fee1,
163-
bytes calldata data
164-
) external {
188+
function algebraFlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
165189
uniswapV3FlashCallback(fee0, fee1, data);
166190
}
167191

168-
function uniswapV3FlashCallback(
169-
uint256 fee0,
170-
uint256 fee1,
171-
bytes calldata data
172-
) public {
192+
function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) public {
173193
// Liquidate unhealthy borrow, exchange seized collateral, return flashloaned funds, and exchange profit
174194
// Decode params
175195
LiquidateToTokensWithFlashSwapVars memory vars = abi.decode(data[4:], (LiquidateToTokensWithFlashSwapVars));
@@ -328,10 +348,10 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
328348
* Each whitelisted redemption strategy has to be checked to not be able to
329349
* call `selfdestruct` with the `delegatecall` call in `redeemCustomCollateral`
330350
*/
331-
function _whitelistRedemptionStrategies(IRedemptionStrategy[] calldata strategies, bool[] calldata whitelisted)
332-
external
333-
onlyOwner
334-
{
351+
function _whitelistRedemptionStrategies(
352+
IRedemptionStrategy[] calldata strategies,
353+
bool[] calldata whitelisted
354+
) external onlyOwner {
335355
require(
336356
strategies.length > 0 && strategies.length == whitelisted.length,
337357
"list of strategies empty or whitelist does not match its length"
@@ -342,6 +362,19 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
342362
}
343363
}
344364

365+
function setExpressRelay(address _expressRelay) external onlyOwner {
366+
expressRelay = IExpressRelay(_expressRelay);
367+
}
368+
369+
function setPoolLens(PoolLens _poolLens) external onlyOwner {
370+
lens = _poolLens;
371+
}
372+
373+
function setHealthFactorThreshold(uint256 _healthFactorThreshold) external onlyOwner {
374+
require(_healthFactorThreshold <= 1e18, "Invalid Health Factor Threshold");
375+
healthFactorThreshold = _healthFactorThreshold;
376+
}
377+
345378
/**
346379
* @dev Redeem "special" collateral tokens (before swapping the output for borrowed tokens to be repaid via Uniswap).
347380
* Public visibility because we have to call this function externally if called from a payable IonicLiquidator function (for some reason delegatecall fails when called with msg.value > 0).

0 commit comments

Comments
 (0)