// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(
        uint256 a,
        uint256 b
    ) internal pure returns (bool, uint256) {
        uint256 c = a + b;
        if (c < a) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(
        uint256 a,
        uint256 b
    ) internal pure returns (bool, uint256) {
        if (b > a) return (false, 0);
        return (true, a - b);
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(
        uint256 a,
        uint256 b
    ) internal pure returns (bool, uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) return (true, 0);
        uint256 c = a * b;
        if (c / a != b) return (false, 0);
        return (true, c);
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(
        uint256 a,
        uint256 b
    ) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a / b);
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(
        uint256 a,
        uint256 b
    ) internal pure returns (bool, uint256) {
        if (b == 0) return (false, 0);
        return (true, a % b);
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) return 0;
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: division by zero");
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: modulo by zero");
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        return a - b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryDiv}.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        return a % b;
    }
}

interface IERC20 {
    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(
        address recipient,
        uint256 amount
    ) external returns (bool);

    function allowance(
        address owner,
        address spender
    ) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);
}

interface IVaultInterface {
    function execute(
        address,
        bytes memory
    ) external payable returns (bytes memory);
}

interface ISparkPool {
    struct ReserveData {
        //stores the reserve configuration
        ReserveConfigurationMap configuration;
        //the liquidity index. Expressed in ray
        uint128 liquidityIndex;
        //the current supply rate. Expressed in ray
        uint128 currentLiquidityRate;
        //variable borrow index. Expressed in ray
        uint128 variableBorrowIndex;
        //the current variable borrow rate. Expressed in ray
        uint128 currentVariableBorrowRate;
        //the current stable borrow rate. Expressed in ray
        uint128 currentStableBorrowRate;
        //timestamp of last update
        uint40 lastUpdateTimestamp;
        //the id of the reserve. Represents the position in the list of the active reserves
        uint16 id;
        //aToken address
        address aTokenAddress;
        //stableDebtToken address
        address stableDebtTokenAddress;
        //variableDebtToken address
        address variableDebtTokenAddress;
        //address of the interest rate strategy
        address interestRateStrategyAddress;
        //the current treasury balance, scaled
        uint128 accruedToTreasury;
        //the outstanding unbacked aTokens minted through the bridging feature
        uint128 unbacked;
        //the outstanding debt borrowed against this asset in isolation mode
        uint128 isolationModeTotalDebt;
    }

    struct ReserveConfigurationMap {
        uint256 data;
    }

    function getUserAccountData(
        address user
    )
        external
        view
        returns (
            uint256 totalCollateralBase,
            uint256 totalDebtBase,
            uint256 availableBorrowsBase,
            uint256 currentLiquidationThreshold,
            uint256 ltv,
            uint256 healthFactor
        );

    function getReserveData(
        address asset
    ) external view returns (ReserveData memory);
}

interface AggregatorV3Interface {
    function decimals() external view returns (uint8);

    function description() external view returns (string memory);

    function version() external view returns (uint256);

    // getRoundData and latestRoundData should both raise "No data present"
    // if they do not have data to report, instead of returning unset values
    // which could be misinterpreted as actual reported values.
    function getRoundData(
        uint80 _roundId
    )
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );

    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            uint256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
}


interface IsparkSavingsInterface {

    function pool() external view returns (address);

    function wstETH() external view returns (address);

    function savingsAsset() external view returns (address);

    function borrowAsset() external view returns (address);
}

contract SparkStakeMonitor {
    using SafeMath for uint256;
    address public owner;
    address public sparkPool;
    address public daiStakePool;
    address public lidoSparkSavingsStrategy;
    address public chainlinkStEthBaseEth;
    address public chainlinkDaiBaseUSD;
    address public wstETH;
    address public DAI;

    constructor(address _owner) {
        owner = _owner;
        chainlinkStEthBaseEth = 0x86392dC19c0b719886221c78AB11eb8Cf5c52812;
        chainlinkDaiBaseUSD = 0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9;
        lidoSparkSavingsStrategy = 0x71122Cd26c5f1E18826652708C3e00D1cf837DA4; //0x71122Cd26c5f1E18826652708C3e00D1cf837DA4
        sparkPool = IsparkSavingsInterface(lidoSparkSavingsStrategy).pool(); //spark 0xC13e21B648A5Ee794902342038FF3aDAB66BE987#
        daiStakePool = IsparkSavingsInterface(lidoSparkSavingsStrategy).savingsAsset();//0x83F20F44975D03b1b09e64809B757c47f942BEeA; // sdai
        wstETH = IsparkSavingsInterface(lidoSparkSavingsStrategy).wstETH();//0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; //0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0
        DAI = IsparkSavingsInterface(lidoSparkSavingsStrategy).borrowAsset();// 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    }

    function setLidoSparkSavingsStrategy(
        address _lidoSparkSavingsStrategy
    ) external {
        require(
            msg.sender == owner,
            " only owner set Lido Aave Leverage Strategy"
        );
        lidoSparkSavingsStrategy = _lidoSparkSavingsStrategy;
    }

    function getHealthFactor(address _vault) public view returns (uint256) {
        (, , , , , uint256 hf) = ISparkPool(sparkPool).getUserAccountData(_vault);
        return hf;
    }

    function getSparkPoolLiquidity(
        address _underlying
    ) public view returns (uint256) {
        uint256 _cash; // 1e18
        address _aToken;
        ISparkPool.ReserveData memory reserveData;
        reserveData = ISparkPool(sparkPool).getReserveData(_underlying);
        _aToken = reserveData.aTokenAddress;
        _cash = IERC20(_underlying).balanceOf(_aToken);
        return _cash;
    }

    function getSparkWstETHPoolLiquidity() public view returns (uint256) {
        uint256 _cash; // 1e18
        address _aToken;
        ISparkPool.ReserveData memory reserveData;
        reserveData = ISparkPool(sparkPool).getReserveData(wstETH);
        _aToken = reserveData.aTokenAddress;
        _cash = IERC20(wstETH).balanceOf(_aToken);
        return _cash;
    }

    function isSparkWstETHLiquidityInsufficient(
        uint256 _wstEthAmountThreshold
    ) public view returns (bool) {
        uint256 _wstEthCash;
        _wstEthCash = getSparkWstETHPoolLiquidity();
        if (_wstEthAmountThreshold >= _wstEthCash) {
            return true;
        }
        return false;
    }


    function getSparkBorrwRateThreshold(address _underlying) public view returns (uint128) {
        uint128 _currentDaiBorrowRate; //_ethBorrowRate 1e25
        ISparkPool.ReserveData memory reserveData;
        reserveData = ISparkPool(sparkPool).getReserveData(_underlying);
        _currentDaiBorrowRate = reserveData.currentVariableBorrowRate;
        return _currentDaiBorrowRate;
    }


    function getSparkDaiBorrwRateThreshold() public view returns (uint128) {
        uint128 _currentDaiBorrowRate; //_ethBorrowRate 1e25
        _currentDaiBorrowRate = getSparkBorrwRateThreshold(DAI);
        return _currentDaiBorrowRate;
    }


    function isSparkExceedDaiBorrwRateThreshold(
        uint128 _daiBorrowRate
    ) public view returns (bool) {
        uint128 _currentDaiBorrowRate; //_ethBorrowRate 1e25
        _currentDaiBorrowRate = getSparkDaiBorrwRateThreshold();
        if (_currentDaiBorrowRate >= _daiBorrowRate) {
            return true;
        }
        return false;
    }

    function getSparkMarketBorrowRateAndLiquidity(
        address _underlying
    ) public view returns (uint256, uint256) {
        uint128 _currentBorrowRate; //_ethBorrowRate 1e25
        uint256 _Cash; // 1e18
        address _aToken;
        ISparkPool.ReserveData memory reserveData;
        reserveData = ISparkPool(sparkPool).getReserveData(_underlying);
        _currentBorrowRate = reserveData.currentVariableBorrowRate;
        _aToken = reserveData.aTokenAddress;
        _Cash = IERC20(_underlying).balanceOf(_aToken);
        return (_currentBorrowRate, _Cash);
    }

    function hasAwstEthBalance(address _vault) public view returns (bool) {
        uint256 aTokenAoumnt;
        address _aWstEthToken;
        ISparkPool.ReserveData memory reserveData;
        reserveData = ISparkPool(sparkPool).getReserveData(wstETH);
        _aWstEthToken = reserveData.aTokenAddress;
        aTokenAoumnt = IERC20(_aWstEthToken).balanceOf(_vault);

        if (aTokenAoumnt > 0) {
            return true;
        }
        return false;
    }



    function hasSparkStaked(address _vault) public view returns (bool) {
        uint256 _balance;

        _balance = IERC20(daiStakePool).balanceOf(_vault);

        if (_balance > 0) {
            return true;
        }
        return false;
    }

    function getTokenBalance(address underlying, address vault) public view returns (uint256) {
        return IERC20(underlying).balanceOf(vault);
    }

    function getPriceChainLink(
        address _chainlinkAggregator
    ) public view returns (uint256) {
        /*
        (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
        )
        */
        (, uint256 _price, , , ) = AggregatorV3Interface(_chainlinkAggregator)
            .latestRoundData();
        return _price;
    }

    function getDaiPriceFromChainLink() public view returns (uint256) {
        uint256 _price;
        _price = getPriceChainLink(chainlinkDaiBaseUSD);
        return _price;
    }


    function getStEthPriceFromChainLink() public view returns (uint256) {
        uint256 _price;
        _price = getPriceChainLink(chainlinkStEthBaseEth);
        return _price;
    }

    function isDaiPriceUnanchoredFromChainLink(
        uint256 _anchorPrice
    ) public view returns (bool) {
        /*
        (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
        )
        */
        uint256 _price;
        _price = getDaiPriceFromChainLink();
        if (_price <= _anchorPrice) {
            return true;
        }
        return false;
    }

    function isStEthPriceUnanchoredFromChainLink(
        uint256 _anchorPrice
    ) public view returns (bool) {
        /*
        (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
        )
        */
        uint256 _price;
        _price = getStEthPriceFromChainLink();
        if (_price <= _anchorPrice) {
            return true;
        }
        return false;
    }


    function isNotHealth(
        address _vault,
        uint256 _healthFactor
    ) public view returns (bool) {
        uint256 vault_hf;
        vault_hf = getHealthFactor(_vault);
        if (vault_hf <= _healthFactor) {
            return true;
        }
        return false;
    }

    function encodeExitInput()
        internal
        pure
        returns (bytes memory encodedInput)
    {
        return abi.encodeWithSignature("exit()");
    }

    function executeExit(
        address _vault
    ) public view returns (bool canExec, bytes memory execPayload) {
        bytes memory args = encodeExitInput();
        execPayload = abi.encodeWithSelector(
            IVaultInterface(_vault).execute.selector,
            lidoSparkSavingsStrategy,
            args
        );
        return (true, execPayload);
    }

    function checker(
        address _vault,
        uint256 _healthFactorThreshold,
        uint256 _anchorDaiPriceThreshold,
        uint256 _anchorStEthPriceThreshold,
        uint256 _wstEthCashThreshold,
        uint128 _daiBorrowRateThreshold
    ) external view returns (bool canExec, bytes memory execPayload) {
        if (hasSparkStaked(_vault)) {
            if (isNotHealth(_vault, _healthFactorThreshold)) {
                return executeExit(_vault);
            }
            if (isSparkWstETHLiquidityInsufficient(_wstEthCashThreshold)) {
                return executeExit(_vault);
            }
            if (isDaiPriceUnanchoredFromChainLink(_anchorDaiPriceThreshold)) {
                return executeExit(_vault);
            }
            if (isStEthPriceUnanchoredFromChainLink(_anchorStEthPriceThreshold)) {
                return executeExit(_vault);
            }
            if (isSparkExceedDaiBorrwRateThreshold(_daiBorrowRateThreshold)) {
                return executeExit(_vault);
            }
        }
        return (false, bytes("monitor is ok"));
    }
}