"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("@ionicprotocol/types");
const ethers_1 = require("ethers");
const IUniswapV2Factory__factory_1 = require("../../../typechain/factories/IUniswapV2Factory__factory");
const encodeLiquidateTx_1 = __importDefault(require("./encodeLiquidateTx"));
const fundingStrategy_1 = require("./fundingStrategy");
const redemptionStrategy_1 = require("./redemptionStrategy");
const utils_1 = require("./utils");
const index_1 = require("./index");
function getLiquidationPenalty(collateralCToken, liquidationIncentive) {
    return __awaiter(this, void 0, void 0, function* () {
        const protocolSeizeShareMantissa = yield collateralCToken.callStatic.protocolSeizeShareMantissa();
        const feeSeizeShareMantissa = yield collateralCToken.callStatic.feeSeizeShareMantissa();
        return liquidationIncentive.add(protocolSeizeShareMantissa).add(feeSeizeShareMantissa);
    });
}
function getPotentialLiquidation(sdk, borrower, closeFactor, liquidationIncentive, chainLiquidationConfig) {
    return __awaiter(this, void 0, void 0, function* () {
        // Get debt and collateral
        borrower = Object.assign({}, borrower);
        for (let asset of borrower.assets) {
            asset = Object.assign({}, asset);
            asset.borrowBalanceWei = asset.borrowBalance.mul(asset.underlyingPrice).div(utils_1.SCALE_FACTOR_ONE_18_WEI);
            asset.supplyBalanceWei = asset.supplyBalance.mul(asset.underlyingPrice).div(utils_1.SCALE_FACTOR_ONE_18_WEI);
            if (asset.borrowBalance.gt(0))
                borrower.debt.push(asset);
            if (asset.membership && asset.supplyBalance.gt(0))
                borrower.collateral.push(asset);
        }
        if (!borrower.collateral.length) {
            sdk.logger.error(`Borrower has no collateral ${borrower.account}`);
            return null;
        }
        // Sort debt and collateral from highest to lowest ETH value
        borrower.debt.sort((a, b) => (b.borrowBalanceWei.gt(a.borrowBalanceWei) ? 1 : -1));
        borrower.collateral.sort((a, b) => (b.supplyBalanceWei.gt(a.supplyBalanceWei) ? 1 : -1));
        // Check SUPPORTED_INPUT_CURRENCIES (if LIQUIDATION_STRATEGY === "")
        if (chainLiquidationConfig.LIQUIDATION_STRATEGY === types_1.LiquidationStrategy.DEFAULT &&
            chainLiquidationConfig.SUPPORTED_INPUT_CURRENCIES.indexOf(borrower.debt[0].underlyingToken) < 0)
            return null;
        let exchangeToTokenAddress;
        // Check SUPPORTED_OUTPUT_CURRENCIES: replace EXCHANGE_TO_TOKEN_ADDRESS with underlying collateral if underlying collateral is in SUPPORTED_OUTPUT_CURRENCIES
        if (chainLiquidationConfig.SUPPORTED_OUTPUT_CURRENCIES.indexOf(borrower.collateral[0].underlyingToken) >= 0) {
            exchangeToTokenAddress = borrower.collateral[0].underlyingToken;
        }
        else {
            exchangeToTokenAddress = sdk.chainSpecificAddresses.W_TOKEN;
        }
        const debtAsset = borrower.debt[0];
        const collateralAsset = borrower.collateral[0];
        // Get debt and collateral prices
        const debtAssetUnderlyingPrice = debtAsset.underlyingPrice;
        const collateralAssetUnderlyingPrice = collateralAsset.underlyingPrice;
        const debtAssetDecimals = debtAsset.underlyingDecimals;
        const collateralAssetDecimals = collateralAsset.underlyingDecimals;
        const debtAssetUnderlyingToken = debtAsset.underlyingToken;
        // xcDOT: 10 decimals
        const actualCollateral = collateralAsset.supplyBalance;
        // Get liquidation amount
        // USDC: 6 decimals
        let repayAmount = debtAsset.borrowBalance.mul(closeFactor).div(utils_1.SCALE_FACTOR_ONE_18_WEI);
        const penalty = yield getLiquidationPenalty(sdk.createICErc20(collateralAsset.cToken), liquidationIncentive);
        // Scale to 18 decimals
        let liquidationValue = repayAmount.mul(debtAssetUnderlyingPrice).div(ethers_1.BigNumber.from(10).pow(debtAssetDecimals));
        // 18 decimals
        let seizeValue = liquidationValue.mul(penalty).div(utils_1.SCALE_FACTOR_ONE_18_WEI);
        // xcDOT: 10 decimals
        let seizeAmount = seizeValue // 18 decimals
            .mul(utils_1.SCALE_FACTOR_ONE_18_WEI) // -> 36 decimals
            .div(collateralAssetUnderlyingPrice) // -> 18 decimals
            .div((0, utils_1.SCALE_FACTOR_UNDERLYING_DECIMALS)(collateralAsset)); // -> decimals
        // Check if actual collateral is too low to seize seizeAmount; if so, recalculate liquidation amount
        if (seizeAmount.gt(actualCollateral)) {
            // 10 decimals
            seizeAmount = actualCollateral;
            // 18 decimals
            seizeValue = seizeAmount
                // 28 decimals
                .mul(collateralAssetUnderlyingPrice)
                // 18 decimals
                .div(ethers_1.BigNumber.from(10).pow(collateralAssetDecimals));
            // 18 decimals
            liquidationValue = seizeValue.mul(utils_1.SCALE_FACTOR_ONE_18_WEI).div(penalty);
            // 18 decimals
            repayAmount = liquidationValue
                .mul(utils_1.SCALE_FACTOR_ONE_18_WEI)
                .div(debtAssetUnderlyingPrice)
                .div((0, utils_1.SCALE_FACTOR_UNDERLYING_DECIMALS)(debtAsset));
        }
        if (repayAmount.lte(ethers_1.BigNumber.from(0))) {
            sdk.logger.info("Liquidation amount is zero, doing nothing");
            return null;
        }
        // Depending on liquidation strategy
        let debtFundingStrategies = [];
        let debtFundingStrategiesData = [];
        let flashSwapFundingToken = ethers_1.constants.AddressZero;
        if (chainLiquidationConfig.LIQUIDATION_STRATEGY == types_1.LiquidationStrategy.UNISWAP) {
            // chain some liquidation funding strategies
            const fundingStrategiesAndDatas = yield (0, fundingStrategy_1.getFundingStrategiesAndDatas)(sdk, debtAssetUnderlyingToken);
            debtFundingStrategies = fundingStrategiesAndDatas.strategies;
            debtFundingStrategiesData = fundingStrategiesAndDatas.datas;
            flashSwapFundingToken = fundingStrategiesAndDatas.flashSwapFundingToken;
        }
        //  chain some collateral redemption strategies
        const [strategyAndData, tokenPath] = yield (0, redemptionStrategy_1.getRedemptionStrategiesAndDatas)(sdk, borrower.collateral[0].underlyingToken, flashSwapFundingToken);
        let flashSwapPair;
        const uniswapV2Factory = IUniswapV2Factory__factory_1.IUniswapV2Factory__factory.connect(sdk.chainSpecificAddresses.UNISWAP_V2_FACTORY, sdk.provider);
        if (flashSwapFundingToken != sdk.chainConfig.chainAddresses.W_TOKEN) {
            flashSwapPair = yield uniswapV2Factory.callStatic.getPair(flashSwapFundingToken, sdk.chainConfig.chainAddresses.W_TOKEN);
        }
        else {
            // flashSwapFundingToken is the W_TOKEN
            flashSwapPair = yield uniswapV2Factory.callStatic.getPair(flashSwapFundingToken, sdk.chainConfig.chainAddresses.STABLE_TOKEN);
            if (tokenPath.indexOf(flashSwapPair) > 0) {
                // in case the Uniswap pair LP token is on the path of redemptions, we should use
                // another pair because reentrancy checks prevent us from using the pair
                // when inside the execution of a flash swap from the same pair
                flashSwapPair = yield uniswapV2Factory.callStatic.getPair(flashSwapFundingToken, sdk.chainConfig.chainAddresses.W_BTC_TOKEN);
            }
            else {
                sdk.logger.info(`flash swap pair ${flashSwapPair} is not on the token path ${tokenPath}`);
            }
        }
        let expectedGasAmount;
        try {
            expectedGasAmount = yield (0, index_1.estimateGas)(sdk, borrower, repayAmount, strategyAndData, flashSwapPair, chainLiquidationConfig.LIQUIDATION_STRATEGY, debtFundingStrategies, debtFundingStrategiesData);
        }
        catch (_a) {
            expectedGasAmount = ethers_1.BigNumber.from(750000);
        }
        // Get gas fee
        const gasPrice = yield sdk.provider.getGasPrice();
        const expectedGasFee = gasPrice.mul(expectedGasAmount);
        // calculate min profits
        const minProfitAmountEth = expectedGasFee.add(chainLiquidationConfig.MINIMUM_PROFIT_NATIVE);
        // const minSeizeAmount = liquidationValueWei.add(minProfitAmountEth).mul(SCALE_FACTOR_ONE_18_WEI).div(outputPrice);
        if (seizeValue.lt(minProfitAmountEth)) {
            sdk.logger.info(`Seize amount of ${ethers_1.utils.formatEther(seizeValue)} less than min break even of ${ethers_1.utils.formatEther(minProfitAmountEth)}, doing nothing`);
            return null;
        }
        return yield (0, encodeLiquidateTx_1.default)(sdk, chainLiquidationConfig.LIQUIDATION_STRATEGY, borrower, exchangeToTokenAddress, strategyAndData, repayAmount, flashSwapPair, debtFundingStrategies, debtFundingStrategiesData);
    });
}
exports.default = getPotentialLiquidation;
