contract FlashLoanLiquidateWePiggy is FlashLoanReceiverBase {

    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    IComptroller public comptroller;
    IUniswapV2Router02 public uniswapRouter;
    bool public transferAtOnce;

    constructor(address _addressProvider, address _addressComptroller, address _addressUniswapRouter) FlashLoanReceiverBase(_addressProvider) public {
        comptroller = IComptroller(_addressComptroller);
        uniswapRouter = IUniswapV2Router02(_addressUniswapRouter);
    }

    /**
        This function is called after your contract has received the flash loaned amount
     */
    function executeOperation(
        address _reserve,
        uint256 _amount,
        uint256 _fee,
        bytes calldata _params
    )
    external
    override
    {
        require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance, was the flashLoan successful?");

        // pTokenBorrow 要被清算的pToken ; pTokenCollateral 抵押物pToken ; borrower 借款人 ; repayAmount 清算数量 ; path uniswap的交易路径
        (address _pTokenBorrow,address _pTokenCollateral,address borrower, uint repayAmount,address[] memory path) = abi.decode(_params, (address, address, address, uint, address[]));

        IPToken pTokenBorrow = IPToken(_pTokenBorrow);
        IPToken pTokenCollateral = IPToken(_pTokenCollateral);

        uint totalDebt = _amount.add(_fee);

        //enterMarket
        address[] memory pTokens = new address[](2);
        pTokens[0] = _pTokenBorrow;
        pTokens[1] = _pTokenCollateral;
        comptroller.enterMarkets(pTokens);

        if (_isEth(pTokenBorrow.symbol()) && _isEth(pTokenCollateral.symbol())) {

            _liquidateETHAndRepayETH(IPETH(_pTokenBorrow), IPETH(_pTokenCollateral), borrower, repayAmount, path, totalDebt);

        } else if (_isEth(pTokenBorrow.symbol()) && !_isEth(pTokenCollateral.symbol())) {

            _liquidateETHAndRepayERC20(IPETH(_pTokenBorrow), IPERC20(_pTokenCollateral), borrower, repayAmount, path, totalDebt);

        } else if (!_isEth(pTokenBorrow.symbol()) && _isEth(pTokenCollateral.symbol())) {

            _liquidateERC20AndRepayETH(IPERC20(_pTokenBorrow), IPETH(_pTokenCollateral), borrower, repayAmount, path, totalDebt);

        } else if (!_isEth(pTokenBorrow.symbol()) && !_isEth(pTokenCollateral.symbol())) {

            _liquidateERC20AndRepayERC20(IPERC20(_pTokenBorrow), IPERC20(_pTokenCollateral), borrower, repayAmount, path, totalDebt);
        }


        transferFundsBackToPoolInternal(_reserve, totalDebt);
    }

    /**
    *  _asset 需要借款的代币的合约地址，ETH为 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
    *  amount 需要借款的金额
    */
    function liquidate(address _asset, uint amount, bytes memory data) public {
        ILendingPool lendingPool = ILendingPool(addressesProvider.getLendingPool());
        lendingPool.flashLoan(address(this), _asset, amount, data);

        if (transferAtOnce) {
            _transferInternal(_asset, msg.sender, getBalanceInternal(address(this), _asset));
        }
    }

    function transfer(address _asset, address payable _to, uint amount) public onlyOwner {
        _transferInternal(_asset, _to, amount);
    }

    function setTransferAtOnce(bool _transferAtOnce) public onlyOwner {
        transferAtOnce = _transferAtOnce;
    }

    function setComptroller(address _addressComptroller) public onlyOwner {
        comptroller = IComptroller(_addressComptroller);
    }


    // 清算ETH && 赎回ETH
    function _liquidateETHAndRepayETH(
        IPETH pTokenBorrow,
        IPETH pTokenCollateral,
        address borrower,
        uint repayAmount,
        address[] memory path,
        uint totalDebt
    )
    internal
    {

        address self = address(this);

        //计算清算的值
        (,uint seized) = comptroller.liquidateCalculateSeizeTokens(address(pTokenBorrow), address(pTokenCollateral), repayAmount);

        uint beforeBalance = pTokenCollateral.balanceOf(self);

        //调用清算方法
        pTokenBorrow.liquidateBorrow{value : repayAmount}(borrower, pTokenCollateral);

        uint afterBalance = pTokenCollateral.balanceOf(self);
        if (afterBalance.sub(beforeBalance) < seized) {
            seized = afterBalance.sub(beforeBalance);
        }

        //赎回ETH
        pTokenCollateral.redeem(seized);

    }

    // 清算ETH && 赎回ERC20 ， 得到 ETH
    function _liquidateETHAndRepayERC20(
        IPETH pTokenBorrow,
        IPERC20 pTokenCollateral,
        address borrower,
        uint repayAmount,
        address[] memory path,
        uint totalDebt
    )
    internal
    {

        address self = address(this);

        //计算清算的值
        (,uint seized) = comptroller.liquidateCalculateSeizeTokens(address(pTokenBorrow), address(pTokenCollateral), repayAmount);

        uint beforeBalance = pTokenCollateral.balanceOf(self);

        //调用清算方法
        pTokenBorrow.liquidateBorrow{value : repayAmount}(borrower, pTokenCollateral);

        uint afterBalance = pTokenCollateral.balanceOf(self);
        if (afterBalance.sub(beforeBalance) < seized) {
            seized = afterBalance.sub(beforeBalance);
        }

        //赎回ERC20
        pTokenCollateral.redeem(seized);

        //去 uniSwap等交易所进行交易，获得 ETH
        IERC20 erc20 = IERC20(pTokenCollateral.underlying());
        uint amountIn = erc20.balanceOf(self);
        _approveMax(address(erc20), address(uniswapRouter), amountIn);

        uniswapRouter.swapExactTokensForETH(amountIn, totalDebt, path, self, block.timestamp);

    }

    // 清算ERC20 && 赎回ETH ， 得到 ERC20  --> OK
    function _liquidateERC20AndRepayETH(
        IPERC20 pTokenBorrow,
        IPETH pTokenCollateral,
        address borrower,
        uint repayAmount,
        address[] memory path,
        uint totalDebt
    )
    internal
    {
        address self = address(this);

        //计算清算的值
        (,uint seized) = comptroller.liquidateCalculateSeizeTokens(address(pTokenBorrow), address(pTokenCollateral), repayAmount);

        uint beforeBalance = pTokenCollateral.balanceOf(self);

        //调用清算方法
        IERC20 erc20 = IERC20(pTokenBorrow.underlying());
        _approveMax(address(erc20), address(pTokenBorrow), repayAmount);
        pTokenBorrow.liquidateBorrow(borrower, repayAmount, pTokenCollateral);

        uint afterBalance = pTokenCollateral.balanceOf(self);
        if (afterBalance.sub(beforeBalance) < seized) {
            seized = afterBalance.sub(beforeBalance);
        }

        //赎回ETH
        pTokenCollateral.redeem(seized);

        //去 uniSwap等交易所进行交易，获得 对应的token
        uint amountIn = self.balance;
        uniswapRouter.swapExactETHForTokens{value : amountIn}(totalDebt, path, self, block.timestamp);

    }

    // 清算ERC20 && 赎回ERC20  --> OK
    function _liquidateERC20AndRepayERC20(
        IPERC20 pTokenBorrow,
        IPERC20 pTokenCollateral,
        address borrower,
        uint repayAmount,
        address[] memory path,
        uint totalDebt
    )
    internal
    {

        address self = address(this);

        //计算清算的值
        (,uint seized) = comptroller.liquidateCalculateSeizeTokens(address(pTokenBorrow), address(pTokenCollateral), repayAmount);

        uint beforeBalance = pTokenCollateral.balanceOf(self);

        //调用清算方法
        IERC20 erc20 = IERC20(pTokenBorrow.underlying());
        _approveMax(address(erc20), address(pTokenBorrow), repayAmount);
        pTokenBorrow.liquidateBorrow(borrower, repayAmount, pTokenCollateral);

        uint afterBalance = pTokenCollateral.balanceOf(self);
        if (afterBalance.sub(beforeBalance) < seized) {
            seized = afterBalance.sub(beforeBalance);
        }

        //赎回ERC20
        pTokenCollateral.redeem(seized);

        //去 uniSwap等交易所进行交易，获得 ERC20。（不相同的时候进行交易）
        if (pTokenBorrow.underlying() != pTokenCollateral.underlying()) {
            IERC20 erc20 = IERC20(pTokenCollateral.underlying());
            uint amountIn = erc20.balanceOf(self);
            _approveMax(address(erc20), address(uniswapRouter), amountIn);
            uniswapRouter.swapExactTokensForTokens(amountIn, totalDebt, path, self, block.timestamp);
        }

    }

    function _transferInternal(address _asset, address payable _to, uint amount) internal {
        address self = address(this);

        uint balance = getBalanceInternal(self, _asset);
        if (balance < amount) {
            amount = balance;
        }

        transferInternal(_to, _asset, amount);
    }

    function _isEth(string memory symbol) internal pure returns (bool){
        return _compareStrings(symbol, "pETH");
    }

    function _approveMax(address _asset, address _spender, uint amount) internal {
        IERC20 erc20 = IERC20(_asset);

        uint allowance = erc20.allowance(address(this), _spender);
        if (allowance < amount) {
            uint MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
            erc20.safeApprove(_spender, MAX_INT);
        }
    }

    function _compareStrings(string memory a, string memory b) internal pure returns (bool) {
        return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
    }


}
function _liquidateERC20AndRepayERC20(
    IPERC20 pTokenBorrow,
    IPERC20 pTokenCollateral,
    address borrower,
    uint repayAmount,
    address[] memory path,
    uint totalDebt
)
internal
{

    address self = address(this);

    //计算清算的值
    (,uint seized) = comptroller.liquidateCalculateSeizeTokens(address(pTokenBorrow), address(pTokenCollateral), repayAmount);

    uint beforeBalance = pTokenCollateral.balanceOf(self);

    //调用清算方法
    IERC20 erc20 = IERC20(pTokenBorrow.underlying());
    _approveMax(address(erc20), address(pTokenBorrow), repayAmount);
    pTokenBorrow.liquidateBorrow(borrower, repayAmount, pTokenCollateral);

    uint afterBalance = pTokenCollateral.balanceOf(self);
    if (afterBalance.sub(beforeBalance) < seized) {
        seized = afterBalance.sub(beforeBalance);
    }

    //赎回ERC20
    pTokenCollateral.redeem(seized);

    //去 uniSwap等交易所进行交易，获得 ERC20。（不相同的时候进行交易）
    if (pTokenBorrow.underlying() != pTokenCollateral.underlying()) {
        IERC20 erc20 = IERC20(pTokenCollateral.underlying());
        uint amountIn = erc20.balanceOf(self);
        _approveMax(address(erc20), address(uniswapRouter), amountIn);
        uniswapRouter.swapExactTokensForTokens(amountIn, totalDebt, path, self, block.timestamp);
    }

}

// 清算ERC20 && 赎回ERC20  --> OK