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

Commit

Permalink
Merge pull request #37 from ionicprotocol/feat/iliquidator-interface
Browse files Browse the repository at this point in the history
ILiquidator interface
  • Loading branch information
vminkov authored Jan 16, 2024
2 parents 3b9b0dc + f5d8bbf commit 5c66259
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 183 deletions.
49 changes: 49 additions & 0 deletions contracts/ILiquidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;

import { ICErc20 } from "./compound/CTokenInterfaces.sol";
import "./liquidators/IRedemptionStrategy.sol";
import "./liquidators/IFundsConversionStrategy.sol";

interface ILiquidator {
/**
* borrower The borrower's Ethereum address.
* repayAmount The amount to repay to liquidate the unhealthy loan.
* cErc20 The borrowed CErc20 contract to repay.
* cTokenCollateral The cToken collateral contract to be liquidated.
* minProfitAmount The minimum amount of profit required for execution (in terms of `exchangeProfitTo`). Reverts if this condition is not met.
* redemptionStrategies The IRedemptionStrategy contracts to use, if any, to redeem "special" collateral tokens (before swapping the output for borrowed tokens to be repaid via Uniswap).
* strategyData The data for the chosen IRedemptionStrategy contracts, if any.
*/
struct LiquidateToTokensWithFlashSwapVars {
address borrower;
uint256 repayAmount;
ICErc20 cErc20;
ICErc20 cTokenCollateral;
address flashSwapContract;
uint256 minProfitAmount;
IRedemptionStrategy[] redemptionStrategies;
bytes[] strategyData;
IFundsConversionStrategy[] debtFundingStrategies;
bytes[] debtFundingStrategiesData;
}

function redemptionStrategiesWhitelist(address strategy) external view returns (bool);

function safeLiquidate(
address borrower,
uint256 repayAmount,
ICErc20 cErc20,
ICErc20 cTokenCollateral,
uint256 minOutputAmount
) external returns (uint256);

function safeLiquidateToTokensWithFlashLoan(LiquidateToTokensWithFlashSwapVars calldata vars)
external
returns (uint256);

function _whitelistRedemptionStrategy(IRedemptionStrategy strategy, bool whitelisted) external;

function _whitelistRedemptionStrategies(IRedemptionStrategy[] calldata strategies, bool[] calldata whitelisted)
external;
}
88 changes: 12 additions & 76 deletions contracts/IonicLiquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20

import "./liquidators/IRedemptionStrategy.sol";
import "./liquidators/IFundsConversionStrategy.sol";
import "./ILiquidator.sol";

import "./utils/IW_NATIVE.sol";

Expand All @@ -24,7 +25,7 @@ import { ICErc20 } from "./compound/CTokenInterfaces.sol";
* @notice IonicLiquidator safely liquidates unhealthy borrowers (with flashloan support).
* @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.
*/
contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
contract IonicLiquidator is OwnableUpgradeable, ILiquidator, IUniswapV2Callee {
using AddressUpgradeable for address payable;
using SafeERC20Upgradeable for IERC20Upgradeable;

Expand Down Expand Up @@ -143,30 +144,6 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
return seizedOutputAmount;
}

/**
* borrower The borrower's Ethereum address.
* repayAmount The amount to repay to liquidate the unhealthy loan.
* cErc20 The borrowed CErc20 contract to repay.
* cTokenCollateral The cToken collateral contract to be liquidated.
* minProfitAmount The minimum amount of profit required for execution (in terms of `exchangeProfitTo`). Reverts if this condition is not met.
* redemptionStrategies The IRedemptionStrategy contracts to use, if any, to redeem "special" collateral tokens (before swapping the output for borrowed tokens to be repaid via Uniswap).
* strategyData The data for the chosen IRedemptionStrategy contracts, if any.
*/
struct LiquidateToTokensWithFlashSwapVars {
address borrower;
uint256 repayAmount;
ICErc20 cErc20;
ICErc20 cTokenCollateral;
IUniswapV2Pair flashSwapPair;
uint256 minProfitAmount;
IUniswapV2Router02 uniswapV2RouterForBorrow;
IUniswapV2Router02 uniswapV2RouterForCollateral;
IRedemptionStrategy[] redemptionStrategies;
bytes[] strategyData;
IFundsConversionStrategy[] debtFundingStrategies;
bytes[] debtFundingStrategiesData;
}

/**
* @notice Safely liquidate an unhealthy loan, confirming that at least `minProfitAmount` in NATIVE profit is seized.
* @param vars @see LiquidateToTokensWithFlashSwapVars.
Expand Down Expand Up @@ -202,8 +179,9 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
_flashSwapAmount = fundingAmount;
_flashSwapToken = address(fundingToken);

bool token0IsFlashSwapFundingToken = vars.flashSwapPair.token0() == address(fundingToken);
vars.flashSwapPair.swap(
IUniswapV2Pair flashSwapPair = IUniswapV2Pair(vars.flashSwapContract);
bool token0IsFlashSwapFundingToken = flashSwapPair.token0() == address(fundingToken);
flashSwapPair.swap(
token0IsFlashSwapFundingToken ? fundingAmount : 0,
!token0IsFlashSwapFundingToken ? fundingAmount : 0,
address(this),
Expand Down Expand Up @@ -304,23 +282,14 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
}

// Repay flashloan
return
repayTokenFlashLoan(
vars.cTokenCollateral,
vars.uniswapV2RouterForBorrow,
vars.uniswapV2RouterForCollateral,
vars.redemptionStrategies,
vars.strategyData
);
return repayTokenFlashLoan(vars.cTokenCollateral, vars.redemptionStrategies, vars.strategyData);
}

/**
* @dev Repays token flashloans.
*/
function repayTokenFlashLoan(
ICErc20 cTokenCollateral,
IUniswapV2Router02 uniswapV2RouterForBorrow,
IUniswapV2Router02 uniswapV2RouterForCollateral,
IRedemptionStrategy[] memory redemptionStrategies,
bytes[] memory strategyData
) private returns (address) {
Expand Down Expand Up @@ -360,7 +329,7 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
} else {
// repay amount for the non-borrow side
collateralRequired = UniswapV2Library.getAmountsIn(
uniswapV2RouterForBorrow.factory(),
UNISWAP_V2_ROUTER_02.factory(),
_flashSwapAmount, //flashSwapReturnAmount,
array(address(underlyingCollateral), _flashSwapToken),
flashSwapFee
Expand All @@ -386,7 +355,7 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
} else {
// Get W_NATIVE required to repay flashloan
wethRequired = UniswapV2Library.getAmountsIn(
uniswapV2RouterForBorrow.factory(),
UNISWAP_V2_ROUTER_02.factory(),
flashSwapReturnAmount,
array(W_NATIVE_ADDRESS, _flashSwapToken),
flashSwapFee
Expand All @@ -395,10 +364,10 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {

if (address(underlyingCollateral) != W_NATIVE_ADDRESS) {
// Approve to Uniswap router
justApprove(underlyingCollateral, address(uniswapV2RouterForCollateral), underlyingCollateralSeized);
justApprove(underlyingCollateral, address(UNISWAP_V2_ROUTER_02), underlyingCollateralSeized);

// Swap collateral tokens for W_NATIVE to be repaid via Uniswap router
uniswapV2RouterForCollateral.swapTokensForExactTokens(
UNISWAP_V2_ROUTER_02.swapTokensForExactTokens(
wethRequired,
underlyingCollateralSeized,
array(address(underlyingCollateral), W_NATIVE_ADDRESS),
Expand Down Expand Up @@ -459,7 +428,7 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
uint256 underlyingCollateralSeized,
IRedemptionStrategy strategy,
bytes memory strategyData
) public returns (IERC20Upgradeable, uint256) {
) private returns (IERC20Upgradeable, uint256) {
require(redemptionStrategiesWhitelist[address(strategy)], "only whitelisted redemption strategies can be used");

bytes memory returndata = _functionDelegateCall(
Expand All @@ -474,7 +443,7 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
uint256 inputAmount,
IFundsConversionStrategy strategy,
bytes memory strategyData
) public returns (IERC20Upgradeable, uint256) {
) private returns (IERC20Upgradeable, uint256) {
require(redemptionStrategiesWhitelist[address(strategy)], "only whitelisted redemption strategies can be used");

bytes memory returndata = _functionDelegateCall(
Expand Down Expand Up @@ -523,24 +492,6 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
}
}

/**
* @dev Returns an array containing the parameters supplied.
*/
function array(uint256 a) private pure returns (uint256[] memory) {
uint256[] memory arr = new uint256[](1);
arr[0] = a;
return arr;
}

/**
* @dev Returns an array containing the parameters supplied.
*/
function array(address a) private pure returns (address[] memory) {
address[] memory arr = new address[](1);
arr[0] = a;
return arr;
}

/**
* @dev Returns an array containing the parameters supplied.
*/
Expand All @@ -550,19 +501,4 @@ contract IonicLiquidator is OwnableUpgradeable, IUniswapV2Callee {
arr[1] = b;
return arr;
}

/**
* @dev Returns an array containing the parameters supplied.
*/
function array(
address a,
address b,
address c
) private pure returns (address[] memory) {
address[] memory arr = new address[](3);
arr[0] = a;
arr[1] = b;
arr[2] = c;
return arr;
}
}
68 changes: 7 additions & 61 deletions contracts/IonicUniV3Liquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20

import "./liquidators/IRedemptionStrategy.sol";
import "./liquidators/IFundsConversionStrategy.sol";
import "./ILiquidator.sol";

import "./external/uniswap/IUniswapV3FlashCallback.sol";
import "./external/uniswap/IUniswapV3Pool.sol";
Expand All @@ -21,7 +22,7 @@ import { ICErc20 } from "./compound/CTokenInterfaces.sol";
* @author Veliko Minkov <[email protected]> (https://github.com/vminkov)
* @notice IonicUniV3Liquidator liquidates unhealthy borrowers with flashloan support.
*/
contract IonicUniV3Liquidator is OwnableUpgradeable, IUniswapV3FlashCallback {
contract IonicUniV3Liquidator is OwnableUpgradeable, ILiquidator, IUniswapV3FlashCallback {
using AddressUpgradeable for address payable;
using SafeERC20Upgradeable for IERC20Upgradeable;

Expand Down Expand Up @@ -99,19 +100,6 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, IUniswapV3FlashCallback {
return seizedOutputAmount;
}

struct LiquidateToTokensWithFlashSwapVars {
address borrower;
uint256 repayAmount;
ICErc20 cErc20;
ICErc20 cTokenCollateral;
IUniswapV3Pool flashSwapPool;
uint256 minProfitAmount;
IRedemptionStrategy[] redemptionStrategies;
bytes[] strategyData;
IFundsConversionStrategy[] debtFundingStrategies;
bytes[] debtFundingStrategiesData;
}

function safeLiquidateToTokensWithFlashLoan(LiquidateToTokensWithFlashSwapVars calldata vars)
external
returns (uint256)
Expand Down Expand Up @@ -143,8 +131,9 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, IUniswapV3FlashCallback {
_flashSwapAmount = fundingAmount;
_flashSwapToken = address(fundingToken);

bool token0IsFlashSwapFundingToken = vars.flashSwapPool.token0() == address(fundingToken);
vars.flashSwapPool.flash(
IUniswapV3Pool flashSwapPool = IUniswapV3Pool(vars.flashSwapContract);
bool token0IsFlashSwapFundingToken = flashSwapPool.token0() == address(fundingToken);
flashSwapPool.flash(
address(this),
token0IsFlashSwapFundingToken ? fundingAmount : 0,
!token0IsFlashSwapFundingToken ? fundingAmount : 0,
Expand Down Expand Up @@ -352,7 +341,7 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, IUniswapV3FlashCallback {
uint256 underlyingCollateralSeized,
IRedemptionStrategy strategy,
bytes memory strategyData
) public returns (IERC20Upgradeable, uint256) {
) private returns (IERC20Upgradeable, uint256) {
require(redemptionStrategiesWhitelist[address(strategy)], "only whitelisted redemption strategies can be used");

bytes memory returndata = _functionDelegateCall(
Expand All @@ -367,7 +356,7 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, IUniswapV3FlashCallback {
uint256 inputAmount,
IFundsConversionStrategy strategy,
bytes memory strategyData
) public returns (IERC20Upgradeable, uint256) {
) private returns (IERC20Upgradeable, uint256) {
require(redemptionStrategiesWhitelist[address(strategy)], "only whitelisted redemption strategies can be used");

bytes memory returndata = _functionDelegateCall(
Expand Down Expand Up @@ -415,47 +404,4 @@ contract IonicUniV3Liquidator is OwnableUpgradeable, IUniswapV3FlashCallback {
}
}
}

/**
* @dev Returns an array containing the parameters supplied.
*/
function array(uint256 a) private pure returns (uint256[] memory) {
uint256[] memory arr = new uint256[](1);
arr[0] = a;
return arr;
}

/**
* @dev Returns an array containing the parameters supplied.
*/
function array(address a) private pure returns (address[] memory) {
address[] memory arr = new address[](1);
arr[0] = a;
return arr;
}

/**
* @dev Returns an array containing the parameters supplied.
*/
function array(address a, address b) private pure returns (address[] memory) {
address[] memory arr = new address[](2);
arr[0] = a;
arr[1] = b;
return arr;
}

/**
* @dev Returns an array containing the parameters supplied.
*/
function array(
address a,
address b,
address c
) private pure returns (address[] memory) {
address[] memory arr = new address[](3);
arr[0] = a;
arr[1] = b;
arr[2] = c;
return arr;
}
}
8 changes: 3 additions & 5 deletions contracts/test/AnyLiquidationTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BasePriceOracle } from "../oracles/BasePriceOracle.sol";
import { IERC20Upgradeable } from "openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol";
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

import { IonicLiquidator } from "../IonicLiquidator.sol";
import { IonicLiquidator, ILiquidator } from "../IonicLiquidator.sol";
import { PoolDirectory } from "../PoolDirectory.sol";
import { BaseTest } from "./config/BaseTest.t.sol";
import { AddressesProvider } from "../ionic/AddressesProvider.sol";
Expand Down Expand Up @@ -325,15 +325,13 @@ contract AnyLiquidationTest is BaseTest {
vm.prank(ap.owner());
try
vars.liquidator.safeLiquidateToTokensWithFlashLoan(
IonicLiquidator.LiquidateToTokensWithFlashSwapVars(
ILiquidator.LiquidateToTokensWithFlashSwapVars(
vars.borrower,
vars.repayAmount,
ICErc20(address(vars.debtMarket)),
ICErc20(address(vars.collateralMarket)),
vars.flashSwapPair,
address(vars.flashSwapPair),
0,
IUniswapV2Router02(uniswapRouter), // TODO ASSET_SPECIFIC_ROUTER
IUniswapV2Router02(uniswapRouter), // TODO ASSET_SPECIFIC_ROUTER
vars.strategies,
vars.redemptionDatas,
vars.fundingStrategies,
Expand Down
8 changes: 3 additions & 5 deletions contracts/test/NeondevnetE2ETest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { UniswapLpTokenLiquidator } from "../liquidators/UniswapLpTokenLiquidato
import { IUniswapV2Pair } from "../external/uniswap/IUniswapV2Pair.sol";
import { IUniswapV2Factory } from "../external/uniswap/IUniswapV2Factory.sol";
import { PoolLens } from "../PoolLens.sol";
import { IonicLiquidator } from "../IonicLiquidator.sol";
import { IonicLiquidator, ILiquidator } from "../IonicLiquidator.sol";
import { CErc20 } from "../compound/CToken.sol";
import { ERC20Upgradeable } from "openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import { ICErc20 } from "../compound/CTokenInterfaces.sol";
Expand Down Expand Up @@ -197,15 +197,13 @@ contract NeondevnetE2ETest is WithPool {
IUniswapV2Pair flashSwapPair = IUniswapV2Pair(pairAddress);

vars.liquidator.safeLiquidateToTokensWithFlashLoan(
IonicLiquidator.LiquidateToTokensWithFlashSwapVars(
ILiquidator.LiquidateToTokensWithFlashSwapVars(
accountOne,
4e13,
ICErc20(address(cToken)),
ICErc20(address(cWNeonToken)),
flashSwapPair,
address(flashSwapPair),
0,
uniswapRouter,
uniswapRouter,
vars.strategies,
vars.abis,
vars.fundingStrategies,
Expand Down
Loading

0 comments on commit 5c66259

Please sign in to comment.