// File: @chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  function getRoundData(uint80 _roundId)
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );

  function latestRoundData()
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: contracts/lybra/helpers/LybraHelper.sol



// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.17;

import "../interfaces/Iconfigurator.sol";
import "../interfaces/IEUSD.sol";
import "../interfaces/ILybra.sol";
import "../interfaces/IMiningIncentives.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

interface IStakingRewards {
    function rewardRatio() external view returns (uint256);
}

contract LybraHelper is Ownable {
    Iconfigurator public immutable configurator;
    address[] public pools;
    AggregatorV3Interface public priceFeed;
    IMiningIncentives miningIncentives;
    event BatchClaimingRewardsForUsers(address indexed caller, string desc, uint256 total);

    //priceFeed = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
    constructor(address _config, address _etherOracle, address _miningIncentives) {
        configurator = Iconfigurator(_config);
        priceFeed = AggregatorV3Interface(_etherOracle);
        miningIncentives = IMiningIncentives(_miningIncentives);
    }

    function setPools(address[] memory _pools) external onlyOwner {
        pools = _pools;
    }

    function getAssetPrice(address pool) public view returns (uint256) {
        (,int price, , , ) = priceFeed.latestRoundData();
        return ILybra(pool).getAsset2EtherExchangeRate() * uint(price) / 1e18;
    }

    function getEtherPrice() public view returns(uint256) {
        (,int etherPrice, , , ) = priceFeed.latestRoundData();
        return uint256(etherPrice);
    }

    function getToTalTVL() external view returns(uint256 a) {
        for(uint i; i < pools.length; i++) {
            a += ILybra(pools[i]).totalDepositedAsset() * getAssetPrice(pools[i]);
        }
    }

    function getCollateralRatio(
        address user,
        address pool
    ) public view returns (uint256) {
        ILybra lybraPool = ILybra(pool);
        if (lybraPool.getBorrowedOf(user) == 0) return 1e22;
        return
            (lybraPool.depositedAsset(user) * getAssetPrice(pool) * 1e12) /
            lybraPool.getBorrowedOf(user);
    }

    function getExcessIncomeAmount(
        address pool
    ) external view returns (uint256 assetAmount) {
        ILybra lybraPool = ILybra(pool);
        if (lybraPool.getVaultType() != 0) return 0;
        address asset = lybraPool.getAsset();
        assetAmount =  IERC20(asset).balanceOf(address(pool)) -
            lybraPool.totalDepositedAsset();
    }

    function getOverallCollateralRatio(
        address pool
    ) public view returns (uint256) {
        ILybra lybraPool = ILybra(pool);
        return
            (lybraPool.totalDepositedAsset() * getAssetPrice(pool) * 1e12) /
            lybraPool.getPoolTotalCirculation();
    }

    function getLiquidateableAmount(
        address user,
        address pool
    ) external view returns (uint256 etherAmount, uint256 eusdAmount) {
        ILybra lybraPool = ILybra(pool);
        if (getCollateralRatio(user, pool) > 150 * 1e18) return (0, 0);
        if (
            getCollateralRatio(user, pool) >= 125 * 1e18 ||
            getOverallCollateralRatio(pool) >= 150 * 1e18 || lybraPool.getVaultType() != 0
        ) {
            etherAmount = lybraPool.depositedAsset(user) / 2;
            eusdAmount = (etherAmount * getAssetPrice(pool)) / 1e8;
        } else {
            etherAmount = lybraPool.depositedAsset(user);
            eusdAmount = (etherAmount * getAssetPrice(pool)) / 1e8;
            if (getCollateralRatio(user, pool) >= 1e20) {
                eusdAmount =
                    (eusdAmount * 1e20) /
                    getCollateralRatio(user, pool);
            }
        }
    }

    function getRedeemableAmount(address user, address pool) external view returns (uint256) {
        if (!configurator.isRedemptionProvider(user)) return 0;
        return ILybra(pool).getBorrowedOf(user);
    }

    function getRedeemableAmounts(address[] calldata users, address pool)
        external
        view
        returns (uint256[] memory amounts)
    {
        amounts = new uint256[](users.length);
        for (uint256 i = 0; i < users.length; i++) {
            if (!configurator.isRedemptionProvider(users[i])) {
                amounts[i] = 0;
            } else {
                amounts[i] = ILybra(pool).getBorrowedOf(users[i]);
            }
        }
    }

    function getLiquidateFund(
        address user,
        address pool
    ) external view returns (uint256 eusdAmount) {
        IEUSD token = ILybra(pool).getVaultType() == 0 ? IEUSD(configurator.getEUSDAddress()) : IEUSD(configurator.peUSD());
        uint256 appro = token.allowance(
            user,
            address(pool)
        );
        if (appro == 0) return 0;
        uint256 bal = token.balanceOf(user);
        eusdAmount = appro > bal ? bal : appro;
    }

    function getBuyAbleEarnings(address[] memory users) external view returns(uint256[] memory amounts) {
        amounts = new uint256[](users.length);
        for(uint256 i; i < users.length; i++) {
            if(miningIncentives.isOtherEarningsClaimable(users[i])) {
                amounts[i] = miningIncentives.earned(users[i]);
            }
        }
    }

    function getWithdrawableAmount(
        address user,
        address pool
    ) external view returns (uint256) {
        ILybra lybraPool = ILybra(pool);
        if (lybraPool.getBorrowedOf(user) == 0)
            return lybraPool.depositedAsset(user);
        uint256 safeCollateralRatio = configurator.getSafeCollateralRatio(pool);
        if (getCollateralRatio(user, pool) <= safeCollateralRatio) return 0;
        return
            (lybraPool.depositedAsset(user) *
                (getCollateralRatio(user, pool) - safeCollateralRatio)) /
            getCollateralRatio(user, pool);
    }

    function getEusdMintableAmount(
        address user,
        address pool
    ) external view returns (uint256 eusdAmount) {
        ILybra lybraPool = ILybra(pool);
        uint256 safeCollateralRatio = configurator.getSafeCollateralRatio(pool);
        if (getCollateralRatio(user, pool) <= safeCollateralRatio) return 0;
        return
            (lybraPool.depositedAsset(user) * getAssetPrice(pool)) /
            1e24 /
            safeCollateralRatio -
            lybraPool.getBorrowedOf(user);
    }

    function getStakingPoolAPR(
        address poolAddress,
        address lbr,
        address lpToken
    ) external view returns (uint256 apr) {
        uint256 pool_lp_stake = IERC20(poolAddress).totalSupply();
        uint256 rewardRatio = IStakingRewards(poolAddress).rewardRatio();
        uint256 lp_lbr_amount = IERC20(lbr).balanceOf(lpToken);
        uint256 lp_total_supply = IERC20(lpToken).totalSupply();
        apr =
            (lp_total_supply * rewardRatio * 86_400 * 365 * 1e6) /
            (pool_lp_stake * lp_lbr_amount * 2);
    }

    function getTokenPrice(
        address token,
        address UniPool,
        address wethAddress
    ) external view returns (uint256 price) {
        uint256 token_in_pool = IERC20(token).balanceOf(UniPool);
        uint256 weth_in_pool = IERC20(wethAddress).balanceOf(UniPool);
        price =
            (weth_in_pool * getEtherPrice() * 1e10) /
            token_in_pool;
    }
}

// File: contracts/lybra/interfaces/Iconfigurator.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.17;

interface Iconfigurator {
    function mintVault(address pool) external view returns(bool);
    function mintVaultMaxSupply(address pool) external view returns(uint256);
    function vaultMintPaused(address pool) external view returns(bool);
    function vaultBurnPaused(address pool) external view returns(bool);
    function tokenMiner(address pool) external view returns(bool);
    function getSafeCollateralRatio(address pool) external view returns(uint256);
    function getBadCollateralRatio(address pool) external view returns(uint256);
    function getVaultWeight(address pool) external view returns (uint256);
    function vaultMintFeeApy(address pool) external view returns(uint256);
    function vaultKeeperRatio(address pool) external view returns(uint256);
    function redemptionFee() external view returns(uint256);
    function getEUSDAddress() external view returns(address);
    function peUSD() external view returns(address);
    function eUSDMiningIncentives() external view returns(address);
    function getProtocolRewardsPool() external view returns(address);
    function flashloanFee() external view returns(uint256);
    function getEUSDMaxLocked() external view returns (uint256);
    function stableToken() external view returns (address);
    function isRedemptionProvider(address user) external view returns (bool);
    function becomeRedemptionProvider(bool _bool) external;
    function refreshMintReward(address user) external;
    function distributeRewards() external;
    function hasRole(bytes32 role, address account) external view returns (bool);
}
// File: contracts/lybra/interfaces/IEUSD.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

interface IEUSD {
    function totalSupply() external view returns (uint256);

    function getTotalShares() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function sharesOf(address _account) external view returns (uint256);

    function allowance(
        address owner,
        address spender
    ) external view returns (uint256);

    function approve(address _spender, uint256 _amount) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);

    function transferShares(
        address _recipient,
        uint256 _sharesAmount
    ) external returns (uint256);

    function getSharesByMintedEUSD(
        uint256 _EUSDAmount
    ) external view returns (uint256);

    function getMintedEUSDByShares(
        uint256 _sharesAmount
    ) external view returns (uint256);

    function mint(
        address _recipient,
        uint256 _mintAmount
    ) external returns (uint256 newTotalShares);

    function burnShares(
        address _account,
        uint256 burnAmount
    ) external returns (uint256 newTotalShares);

    function burn(
        address _account,
        uint256 burnAmount
    ) external returns (uint256 newTotalShares);

    function transfer(address to, uint256 amount) external returns (bool);
}

// File: contracts/lybra/interfaces/ILybra.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

interface ILybra {
    function totalDepositedAsset() external view returns (uint256);
    function safeCollateralRatio() external view returns (uint256);
    function depositedAsset(address user) external view returns (uint256);
    function getBorrowedOf(address user) external view returns (uint256);
    function getVaultType() external view returns (uint8);
    function totaldepositedAsset() external view returns (uint256);
    function getPoolTotalCirculation() external view returns (uint256);
    function getAssetPrice() external view returns (uint256);
    function getAsset() external view returns (address);
    function getAsset2EtherExchangeRate() external view returns (uint256);
    function burn(address onBehalfOf, uint256 amount) external;
}

// File: contracts/lybra/interfaces/IMiningIncentives.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

interface IMiningIncentives {
    function totalStaked() external view returns (uint256);
    function stakedOf(address user) external view returns (uint256);
    function earned(address user) external view returns (uint256);
    function isOtherEarningsClaimable(address user) external view returns (bool);
    function esLBR() external view returns (address);
    function LBR() external view returns (address);
    function refreshReward(address _account) external;
}