// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "../PrimaryLendingPlatformLeverageCore.sol";
import "../../interfaces/IPriceProviderAggregator.sol";

/**
 * @title PrimaryLendingPlatformLeverageZksync.
 * @notice The PrimaryLendingPlatformLeverageZksync contract is the contract that allows users to open leveraged positions for zksync network.
 * @dev Contract that allows users to open leveraged positions using the OpenOcean exchange aggregator. Inherit from PrimaryLendingPlatformLeverageCore.
 */
contract PrimaryLendingPlatformLeverageZksync is PrimaryLendingPlatformLeverageCore {
    using SafeERC20Upgradeable for ERC20Upgradeable;

    /**
     * @notice The function to be called when a user wants to leverage their position.
     * @dev Executes a leveraged borrow for the borrower on the specified projectToken using the given lendingToken and update related token's prices.
     *
     * Requirements:
     * - The project token is listed on the platform.
     * - The lending token is listed on the platform.
     * - Notional exposure must be greater than 0.
     * - The lending token must be the same as the current lending token or the current lending token must be address(0).
     * - The user must have a valid position for the given project token and lending token.
     *
     * Effects:
     * - Update price of related tokens.
     * - Calculates the required `lendingTokenCount` based on `notionalExposure`.
     * - Performs a naked borrow using `_nakedBorrow` function.
     * - Approves the transfer of `lendingToken` to the system.
     * - Buys tokens on an exchange aggregator using `_buyOnExchangeAggregator` function.
     * - Collateralizes the loan with the received tokens using `_collateralizeLoan` function.
     * - Defers liquidity check using `_deferLiquidityCheck` function.
     * - Sets the leveraged position flag and type for the borrower.
     * @param projectToken The address of the project token.
     * @param lendingToken The address of the lending token.
     * @param notionalExposure The desired notional exposure for the leverage position.
     * @param marginCollateralAmount The amount of collateral to be added to the position as margin.
     * @param buyCalldata The calldata for buying the project token on the exchange aggregator.
     * @param leverageType The type of leverage position.
     * @param priceIds An array of bytes32 price identifiers to update.
     * @param updateData An array of bytes update data for the corresponding price identifiers.
     */
    function leveragedBorrow(
        address projectToken,
        address lendingToken,
        uint notionalExposure,
        uint marginCollateralAmount,
        bytes memory buyCalldata,
        uint8 leverageType,
        bytes32[] memory priceIds,
        bytes[] calldata updateData
    ) external payable nonReentrant {
        IPriceProviderAggregator(address(primaryLendingPlatform.priceOracle())).updatePrices{value: msg.value}(priceIds, updateData);
        _leveragedBorrow(projectToken, lendingToken, notionalExposure, marginCollateralAmount, buyCalldata, msg.sender, leverageType);
    }

    /**
     * @dev Allows a related contract to borrow funds on behalf of a user to enter a leveraged position and update related token's prices.
     *
     * Requirements:
     * - Caller must be a related contract.
     * - The project token is listed on the platform.
     * - The lending token is listed on the platform.
     * - Notional exposure must be greater than 0.
     * - The lending token must be the same as the current lending token or the current lending token must be address(0).
     * - The user must have a valid position for the given project token and lending token.
     *
     * Effects:
     * - Update price of related tokens.
     * - Calculates the required `lendingTokenCount` based on `notionalExposure`.
     * - Performs a naked borrow using `_nakedBorrow` function.
     * - Approves the transfer of `lendingToken` to the system.
     * - Buys tokens on an exchange aggregator using `_buyOnExchangeAggregator` function.
     * - Collateralizes the loan with the received tokens using `_collateralizeLoan` function.
     * - Defers liquidity check using `_deferLiquidityCheck` function.
     * - Sets the leveraged position flag and type for the borrower.
     * @param projectToken The address of the project token the user wants to invest in.
     * @param lendingToken The address of the lending token used for collateral.
     * @param notionalExposure The notional exposure of the user's investment.
     * @param marginCollateralAmount The amount of collateral to be deposited by the user.
     * @param buyCalldata The calldata used for buying the project token on the DEX.
     * @param borrower The address of the user for whom the funds are being borrowed.
     * @param leverageType The type of leverage position.
     * @param priceIds An array of bytes32 price identifiers to update.
     * @param updateData An array of bytes update data for the corresponding price identifiers.
     */
    function leveragedBorrowFromRelatedContract(
        address projectToken,
        address lendingToken,
        uint notionalExposure,
        uint marginCollateralAmount,
        bytes memory buyCalldata,
        address borrower,
        uint8 leverageType,
        bytes32[] memory priceIds,
        bytes[] calldata updateData
    ) external payable nonReentrant onlyRelatedContracts {
        IPriceProviderAggregator(address(primaryLendingPlatform.priceOracle())).updatePrices{value: msg.value}(priceIds, updateData);
        _leveragedBorrow(projectToken, lendingToken, notionalExposure, marginCollateralAmount, buyCalldata, borrower, leverageType);
    }

    /**
     * @dev Returns the price of a given token in USD after updating related token's prices.
     * @param token The address of the token to get the price of.
     * @return price The price of the token in USD.
     * @param priceIds An array of bytes32 price identifiers to update.
     * @param updateData An array of bytes update data for the corresponding price identifiers.
     */
    function getTokenPriceWithUpdatePrices(
        address token,
        bytes32[] memory priceIds,
        bytes[] calldata updateData
    ) external payable returns (uint price) {
        IPriceProviderAggregator(address(primaryLendingPlatform.priceOracle())).updatePrices{value: msg.value}(priceIds, updateData);
        return getTokenPrice(token);
    }

    /**
     * @notice Calculates the lending token count for a given notional value after updating related token's prices.
     * @param _lendingToken The address of the lending token.
     * @param notionalValue The notional value for which the lending token count is to be calculated.
     * @param priceIds An array of bytes32 price identifiers to update.
     * @param updateData An array of bytes update data for the corresponding price identifiers.
     * @return lendingTokenCount The calculated lending token count.
     */
    function calculateLendingTokenCountWithUpdatePrices(
        address _lendingToken,
        uint notionalValue,
        bytes32[] memory priceIds,
        bytes[] calldata updateData
    ) external payable returns (uint lendingTokenCount) {
        IPriceProviderAggregator(address(primaryLendingPlatform.priceOracle())).updatePrices{value: msg.value}(priceIds, updateData);
        return calculateLendingTokenCount(_lendingToken, notionalValue);
    }

    /**
     * @notice Calculates the margin amount for a given position and safety margin after updating related token's prices.
     *
     * Formula: Margin = ((Notional / LVR) * (1 + SafetyMargin)) - Notional
     * @param projectToken The address of the project token.
     * @param lendingToken The address of the lending token.
     * @param safetyMarginNumerator The numerator of the safety margin ratio.
     * @param safetyMarginDenominator The denominator of the safety margin ratio.
     * @param expAmount The exposure amount.
     * @param priceIds An array of bytes32 price identifiers to update.
     * @param updateData An array of bytes update data for the corresponding price identifiers.
     * @return marginAmount The calculated margin amount.
     */
    function calculateMarginWithUpdatePrices(
        address projectToken,
        address lendingToken,
        uint safetyMarginNumerator,
        uint safetyMarginDenominator,
        uint expAmount,
        bytes32[] memory priceIds,
        bytes[] calldata updateData
    ) external payable returns (uint marginAmount) {
        IPriceProviderAggregator(address(primaryLendingPlatform.priceOracle())).updatePrices{value: msg.value}(priceIds, updateData);
        return calculateMargin(projectToken, lendingToken, safetyMarginNumerator, safetyMarginDenominator, expAmount);
    }

    /**
     * @notice Calculates the safety margin numerator and denominator for a given position, margin, and exposure after updating related token's prices.
     *
     * Formula: Safety Margin = ((Margin + Notional) / (Notional / LVR)) - 1
     * @param projectToken The address of the project token.
     * @param lendingToken The address of the lending token.
     * @param margin The margin amount.
     * @param exp The exposure amount.
     * @param priceIds An array of bytes32 price identifiers to update.
     * @param updateData An array of bytes update data for the corresponding price identifiers.
     * @return safetyMarginNumerator The calculated safety margin numerator.
     * @return safetyMarginDenominator The calculated safety margin denominator.
     */
    function calculateSafetyMarginWithUpdatePrices(
        address projectToken,
        address lendingToken,
        uint margin,
        uint exp,
        bytes32[] memory priceIds,
        bytes[] calldata updateData
    ) external payable returns (uint safetyMarginNumerator, uint safetyMarginDenominator) {
        IPriceProviderAggregator(address(primaryLendingPlatform.priceOracle())).updatePrices{value: msg.value}(priceIds, updateData);
        return calculateSafetyMargin(projectToken, lendingToken, margin, exp);
    }
}
[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"address","name":"projectToken","type":"address"},{"indexed":false,"internalType":"address","name":"lendingToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"notionalExposure","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lendingAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"margin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"addingAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalDepositedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountReceive","type":"uint256"}],"name":"LeveragedBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"exchangeAggregator","type":"address"},{"indexed":true,"internalType":"address","name":"registryAggregator","type":"address"}],"name":"SetExchangeAggregator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newPrimaryLendingPlatform","type":"address"}],"name":"SetPrimaryLendingPlatform","type":"event"},{"inputs":[],"name":"BUFFER_PERCENTAGE","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MODERATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"projectToken","type":"address"},{"internalType":"uint256","name":"marginCollateralCount","type":"uint256"}],"name":"calculateAddingAmount","outputs":[{"internalType":"uint256","name":"addingAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"expAmount","type":"uint256"},{"internalType":"uint256","name":"margin","type":"uint256"},{"internalType":"uint256","name":"borrowAmount","type":"uint256"},{"internalType":"uint256","name":"lvrNumerator","type":"uint256"},{"internalType":"uint256","name":"lvrDenominator","type":"uint256"}],"name":"calculateHF","outputs":[{"internalType":"uint256","name":"hfNumerator","type":"uint256"},{"internalType":"uint256","name":"hfDenominator","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"lendingToken","type":"address"},{"internalType":"uint256","name":"notionalValue","type":"uint256"}],"name":"calculateLendingTokenCount","outputs":[{"internalType":"uint256","name":"lendingTokenCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_lendingToken","type":"address"},{"internalType":"uint256","name":"notionalValue","type":"uint256"},{"internalType":"bytes32[]","name":"priceIds","type":"bytes32[]"},{"internalType":"bytes[]","name":"updateData","type":"bytes[]"}],"name":"calculateLendingTokenCountWithUpdatePrices","outputs":[{"internalType":"uint256","name":"lendingTokenCount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"projectToken","type":"address"},{"internalType":"address","name":"lendingToken","type":"address"},{"internalType":"uint256","name":"safetyMarginNumerator","type":"uint256"},{"internalType":"uint256","name":"safetyMarginDenominator","type":"uint256"},{"internalType":"uint256","name":"expAmount","type":"uint256"}],"name":"calculateMargin","outputs":[{"internalType":"uint256","name":"marginAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"projectToken","type":"address"},{"internalType":"address","name":"lendingToken","type":"address"},{"internalType":"uint256","name":"safetyMarginNumerator","type":"uint256"},{"internalType":"uint256","name":"safetyMarginDenominator","type":"uint256"},{"internalType":"uint256","name":"expAmount","type":"uint256"},{"internalType":"bytes32[]","name":"priceIds","type":"bytes32[]"},{"internalType":"bytes[]","name":"updateData","type":"bytes[]"}],"name":"calculateMarginWithUpdatePrices","outputs":[{"internalType":"uint256","name":"marginAmount","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"projectToken","type":"address"},{"internalType":"address","name":"lendingToken","type":"address"},{"internalType":"uint256","name":"margin","type":"uint256"},{"internalType":"uint256","name":"exp","type":"uint256"}],"name":"calculateSafetyMargin","outputs":[{"internalType":"uint256","name":"safetyMarginNumerator","type":"uint256"},{"internalType":"uint256","name":"safetyMarginDenominator","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"projectToken","type":"address"},{"internalType":"address","name":"lendingToken","type":"address"},{"internalType":"uint256","name":"margin","type":"uint256"},{"internalType":"uint256","name":"exp","type":"uint256"},{"internalType":"bytes32[]","name":"priceIds","type":"bytes32[]"},{"internalType":"bytes[]","name":"updateData","type":"bytes[]"}],"name":"calculateSafetyMarginWithUpdatePrices","outputs":[{"internalType":"uint256","name":"safetyMarginNumerator","type":"uint256"},{"internalType":"uint256","name":"safetyMarginDenominator","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"address","name":"projectToken","type":"address"}],"name":"deleteLeveragePosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exchangeAggregator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"address","name":"projectToken","type":"address"}],"name":"getLeverageType","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenPrice","outputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"bytes32[]","name":"priceIds","type":"bytes32[]"},{"internalType":"bytes[]","name":"updateData","type":"bytes[]"}],"name":"getTokenPriceWithUpdatePrices","outputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pit","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isLeveragePosition","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"margin","type":"uint256"},{"internalType":"uint256","name":"exp","type":"uint256"},{"internalType":"uint256","name":"lvrNumerator","type":"uint256"},{"internalType":"uint256","name":"lvrDenominator","type":"uint256"}],"name":"isValidCollateralization","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"projectToken","type":"address"},{"internalType":"address","name":"lendingToken","type":"address"},{"internalType":"uint256","name":"notionalExposure","type":"uint256"},{"internalType":"uint256","name":"marginCollateralAmount","type":"uint256"},{"internalType":"bytes","name":"buyCalldata","type":"bytes"},{"internalType":"uint8","name":"leverageType","type":"uint8"},{"internalType":"bytes32[]","name":"priceIds","type":"bytes32[]"},{"internalType":"bytes[]","name":"updateData","type":"bytes[]"}],"name":"leveragedBorrow","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"projectToken","type":"address"},{"internalType":"address","name":"lendingToken","type":"address"},{"internalType":"uint256","name":"notionalExposure","type":"uint256"},{"internalType":"uint256","name":"marginCollateralAmount","type":"uint256"},{"internalType":"bytes","name":"buyCalldata","type":"bytes"},{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint8","name":"leverageType","type":"uint8"},{"internalType":"bytes32[]","name":"priceIds","type":"bytes32[]"},{"internalType":"bytes[]","name":"updateData","type":"bytes[]"}],"name":"leveragedBorrowFromRelatedContract","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"primaryLendingPlatform","outputs":[{"internalType":"contract IPrimaryLendingPlatform","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"registryAggregator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"exchangeAggregatorAddress","type":"address"},{"internalType":"address","name":"registryAggregatorAddress","type":"address"}],"name":"setExchangeAggregator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newPrimaryLendingPlatform","type":"address"}],"name":"setPrimaryLendingPlatformAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"typeOfLeveragePosition","outputs":[{"internalType":"enum PrimaryLendingPlatformLeverageCore.LeverageType","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}]