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

Commit c2275f5

Browse files
authored
Merge pull request #66 from ionicprotocol/feat/pyth-integration
Feat/pyth integration
2 parents 2d47d49 + e563751 commit c2275f5

8 files changed

+397
-10
lines changed

contracts/ILiquidator.sol

+6
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,10 @@ interface ILiquidator {
4646

4747
function _whitelistRedemptionStrategies(IRedemptionStrategy[] calldata strategies, bool[] calldata whitelisted)
4848
external;
49+
50+
function setExpressRelay(address _expressRelay) external;
51+
52+
function setPoolLens(address _poolLens) external;
53+
54+
function setHealthFactorThreshold(uint256 _healthFactorThreshold) external;
4955
}

contracts/IonicLiquidator.sol

+69-4
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,23 @@ import "./external/uniswap/UniswapV2Library.sol";
1919

2020
import { ICErc20 } from "./compound/CTokenInterfaces.sol";
2121

22+
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelay.sol";
23+
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelayFeeReceiver.sol";
24+
25+
import "./PoolLens.sol";
26+
2227
/**
2328
* @title IonicLiquidator
2429
* @author David Lucid <[email protected]> (https://github.com/davidlucid)
2530
* @notice IonicLiquidator safely liquidates unhealthy borrowers (with flashloan support).
2631
* @dev Do not transfer NATIVE or tokens directly to this address. Only send NATIVE here when using a method, and only approve tokens for transfer to here when using a method. Direct NATIVE transfers will be rejected and direct token transfers will be lost.
2732
*/
28-
contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee {
33+
contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee, IExpressRelayFeeReceiver {
2934
using AddressUpgradeable for address payable;
3035
using SafeERC20Upgradeable for IERC20Upgradeable;
3136

37+
event VaultReceivedETH(address sender, uint256 amount, bytes permissionKey);
38+
3239
/**
3340
* @dev W_NATIVE contract address.
3441
*/
@@ -64,6 +71,27 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee {
6471
*/
6572
uint8 public flashSwapFee;
6673

74+
/**
75+
* @dev Addres of Pyth Express Relay for preventing value leakage in liquidations.
76+
*/
77+
IExpressRelay public expressRelay;
78+
/**
79+
* @dev Pool Lens.
80+
*/
81+
PoolLens public lens;
82+
/**
83+
* @dev Health Factor below which PER permissioning is bypassed.
84+
*/
85+
uint256 public healthFactorThreshold;
86+
87+
modifier onlyPERPermissioned(address borrower, ICErc20 cToken) {
88+
uint256 currentHealthFactor = lens.getHealthFactor(borrower, cToken.comptroller());
89+
if (currentHealthFactor > healthFactorThreshold) {
90+
require(expressRelay.isPermissioned(address(this), abi.encode(borrower)), "invalid liquidation");
91+
}
92+
_;
93+
}
94+
6795
function initialize(
6896
address _wtoken,
6997
address _uniswapV2router,
@@ -119,15 +147,21 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee {
119147
ICErc20 cErc20,
120148
ICErc20 cTokenCollateral,
121149
uint256 minOutputAmount
122-
) external returns (uint256) {
150+
) external onlyPERPermissioned(borrower, cTokenCollateral) returns (uint256) {
123151
// Transfer tokens in, approve to cErc20, and liquidate borrow
124152
require(repayAmount > 0, "Repay amount (transaction value) must be greater than 0.");
125153
IERC20Upgradeable underlying = IERC20Upgradeable(cErc20.underlying());
126154
underlying.safeTransferFrom(msg.sender, address(this), repayAmount);
127155
justApprove(underlying, address(cErc20), repayAmount);
128156
require(cErc20.liquidateBorrow(borrower, repayAmount, address(cTokenCollateral)) == 0, "Liquidation failed.");
129-
// Transfer seized amount to sender
130-
return transferSeizedFunds(address(cTokenCollateral), minOutputAmount);
157+
158+
// Redeem seized cTokens for underlying asset
159+
uint256 seizedCTokenAmount = cTokenCollateral.balanceOf(address(this));
160+
require(seizedCTokenAmount > 0, "No cTokens seized.");
161+
uint256 redeemResult = cTokenCollateral.redeem(seizedCTokenAmount);
162+
require(redeemResult == 0, "Error calling redeeming seized cToken: error code not equal to 0");
163+
164+
return transferSeizedFunds(address(cTokenCollateral.underlying()), minOutputAmount);
131165
}
132166

133167
/**
@@ -150,6 +184,7 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee {
150184
*/
151185
function safeLiquidateToTokensWithFlashLoan(LiquidateToTokensWithFlashSwapVars calldata vars)
152186
external
187+
onlyPERPermissioned(vars.borrower, vars.cTokenCollateral)
153188
returns (uint256)
154189
{
155190
// Input validation
@@ -199,6 +234,23 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee {
199234
require(payable(msg.sender).isContract(), "Sender is not a contract.");
200235
}
201236

237+
/**
238+
* @notice receiveAuctionProceedings function - receives native token from the express relay
239+
* You can use permission key to distribute the received funds to users who got liquidated, LPs, etc...
240+
*/
241+
function receiveAuctionProceedings(bytes calldata permissionKey) external payable {
242+
emit VaultReceivedETH(msg.sender, msg.value, permissionKey);
243+
}
244+
245+
function withdrawAll() external onlyOwner {
246+
uint256 balance = address(this).balance;
247+
require(balance > 0, "No Ether left to withdraw");
248+
249+
// Transfer all Ether to the owner
250+
(bool sent, ) = msg.sender.call{ value: balance }("");
251+
require(sent, "Failed to send Ether");
252+
}
253+
202254
/**
203255
* @dev Callback function for Uniswap flashloans.
204256
*/
@@ -419,6 +471,19 @@ contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee {
419471
}
420472
}
421473

474+
function setExpressRelay(address _expressRelay) external onlyOwner {
475+
expressRelay = IExpressRelay(_expressRelay);
476+
}
477+
478+
function setPoolLens(address _poolLens) external onlyOwner {
479+
lens = PoolLens(_poolLens);
480+
}
481+
482+
function setHealthFactorThreshold(uint256 _healthFactorThreshold) external onlyOwner {
483+
require(_healthFactorThreshold <= 1e18, "Invalid Health Factor Threshold");
484+
healthFactorThreshold = _healthFactorThreshold;
485+
}
486+
422487
/**
423488
* @dev Redeem "special" collateral tokens (before swapping the output for borrowed tokens to be repaid via Uniswap).
424489
* 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

+67-4
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,20 @@ import { IUniswapV3Quoter } from "./external/uniswap/quoter/interfaces/IUniswapV
1616

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

19+
import "./PoolLens.sol";
20+
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelay.sol";
21+
import "@pythnetwork/express-relay-sdk-solidity/IExpressRelayFeeReceiver.sol";
22+
1923
/**
2024
* @title IonicUniV3Liquidator
2125
* @author Veliko Minkov <[email protected]> (https://github.com/vminkov)
2226
* @notice IonicUniV3Liquidator liquidates unhealthy borrowers with flashloan support.
2327
*/
24-
contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3FlashCallback {
28+
contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3FlashCallback, IExpressRelayFeeReceiver {
2529
using AddressUpgradeable for address payable;
2630
using SafeERC20Upgradeable for IERC20Upgradeable;
2731

32+
event VaultReceivedETH(address sender, uint256 amount, bytes permissionKey);
2833
/**
2934
* @dev Cached liquidator profit exchange source.
3035
* ERC20 token address or the zero address for NATIVE.
@@ -48,6 +53,27 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
4853
mapping(address => bool) public redemptionStrategiesWhitelist;
4954
IUniswapV3Quoter public quoter;
5055

56+
/**
57+
* @dev Addres of Pyth Express Relay for preventing value leakage in liquidations.
58+
*/
59+
IExpressRelay public expressRelay;
60+
/**
61+
* @dev Pool Lens.
62+
*/
63+
PoolLens public lens;
64+
/**
65+
* @dev Health Factor below which PER permissioning is bypassed.
66+
*/
67+
uint256 public healthFactorThreshold;
68+
69+
modifier onlyPERPermissioned(address borrower, ICErc20 cToken) {
70+
uint256 currentHealthFactor = lens.getHealthFactor(borrower, cToken.comptroller());
71+
if (currentHealthFactor > healthFactorThreshold) {
72+
require(expressRelay.isPermissioned(address(this), abi.encode(borrower)), "invalid liquidation");
73+
}
74+
_;
75+
}
76+
5177
function initialize(address _wtoken, address _quoter) external initializer {
5278
__Ownable_init();
5379
W_NATIVE_ADDRESS = _wtoken;
@@ -68,15 +94,21 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
6894
ICErc20 cErc20,
6995
ICErc20 cTokenCollateral,
7096
uint256 minOutputAmount
71-
) external returns (uint256) {
97+
) external onlyPERPermissioned(borrower, cTokenCollateral) returns (uint256) {
7298
// Transfer tokens in, approve to cErc20, and liquidate borrow
7399
require(repayAmount > 0, "Repay amount (transaction value) must be greater than 0.");
74100
IERC20Upgradeable underlying = IERC20Upgradeable(cErc20.underlying());
75101
underlying.safeTransferFrom(msg.sender, address(this), repayAmount);
76102
underlying.approve(address(cErc20), repayAmount);
77103
require(cErc20.liquidateBorrow(borrower, repayAmount, address(cTokenCollateral)) == 0, "Liquidation failed.");
78-
// Transfer seized amount to sender
79-
return transferSeizedFunds(address(cTokenCollateral), minOutputAmount);
104+
105+
// Redeem seized cTokens for underlying asset
106+
uint256 seizedCTokenAmount = cTokenCollateral.balanceOf(address(this));
107+
require(seizedCTokenAmount > 0, "No cTokens seized.");
108+
uint256 redeemResult = cTokenCollateral.redeem(seizedCTokenAmount);
109+
require(redeemResult == 0, "Error calling redeeming seized cToken: error code not equal to 0");
110+
111+
return transferSeizedFunds(address(cTokenCollateral.underlying()), minOutputAmount);
80112
}
81113

82114
/**
@@ -95,6 +127,7 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
95127

96128
function safeLiquidateToTokensWithFlashLoan(LiquidateToTokensWithFlashSwapVars calldata vars)
97129
external
130+
onlyPERPermissioned(vars.borrower, vars.cTokenCollateral)
98131
returns (uint256)
99132
{
100133
// Input validation
@@ -144,6 +177,23 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
144177
require(payable(msg.sender).isContract(), "Sender is not a contract.");
145178
}
146179

180+
/**
181+
* @notice receiveAuctionProceedings function - receives native token from the express relay
182+
* You can use permission key to distribute the received funds to users who got liquidated, LPs, etc...
183+
*/
184+
function receiveAuctionProceedings(bytes calldata permissionKey) external payable {
185+
emit VaultReceivedETH(msg.sender, msg.value, permissionKey);
186+
}
187+
188+
function withdrawAll() external onlyOwner {
189+
uint256 balance = address(this).balance;
190+
require(balance > 0, "No Ether left to withdraw");
191+
192+
// Transfer all Ether to the owner
193+
(bool sent, ) = msg.sender.call{ value: balance }("");
194+
require(sent, "Failed to send Ether");
195+
}
196+
147197
/**
148198
* @dev Callback function for Uniswap flashloans.
149199
*/
@@ -341,6 +391,19 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3Flas
341391
}
342392
}
343393

394+
function setExpressRelay(address _expressRelay) external onlyOwner {
395+
expressRelay = IExpressRelay(_expressRelay);
396+
}
397+
398+
function setPoolLens(address _poolLens) external onlyOwner {
399+
lens = PoolLens(_poolLens);
400+
}
401+
402+
function setHealthFactorThreshold(uint256 _healthFactorThreshold) external onlyOwner {
403+
require(_healthFactorThreshold <= 1e18, "Invalid Health Factor Threshold");
404+
healthFactorThreshold = _healthFactorThreshold;
405+
}
406+
344407
/**
345408
* @dev Redeem "special" collateral tokens (before swapping the output for borrowed tokens to be repaid via Uniswap).
346409
* 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)