// File: contracts/WiseLending.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

/**
 * @author Christoph Krpoun
 * @author René Hochmuth
 * @author Vitally Marinchenko
 */

import "./PoolManager.sol";

contract WiseLending is PoolManager {

    receive()
        external
        payable
    {
        if (msg.sender == WETH_ADDRESS) {
            return;
        }

        payable(master).transfer(
            msg.value
        );
    }

    /**
     * @dev Runs the LASA algorithm known as
     * Lending Automated Scaling Algorithm
     * and updates pool data based on token
     */
    modifier syncPool(
        address _poolToken
    ) {
        _syncPoolBeforeCodeExecution(
            _poolToken
        );
        _;
        _syncPoolAfterCodeExecution(
            _poolToken
        );
    }

    constructor(
        address _master,
        address _wiseOracleHubAddress,
        address _nftContract,
        address _wethContract
    )
        WiseLendingDeclaration(
            _master,
            _wiseOracleHubAddress,
            _nftContract,
            _wethContract
        )
    {}

    function _syncPoolBeforeCodeExecution(
        address _poolToken
    )
        private
    {
        if (veryfiedIsolationPool[msg.sender] == true) {
            return;
        }

        if (msg.sender == AAVE_HUB) {
            return;
        }

        _preparePool(
            _poolToken
        );
    }

    function _syncPoolAfterCodeExecution(
        address _poolToken
    )
        private
    {
        _newBorrowRate(
            _poolToken
        );

        _setLastUpdated(
            _poolToken
        );

        WISE_ORACLE.setPreviousValue(
            _poolToken
        );
    }

    /**
     * @dev Allows to give permission for onBehalf function
     * execution, allowing 3rd party to perform actions such as
     * borrowOnBehalf and withdrawOnBehalf with amount limit
     */
    function approve(
        address _spender,
        address _poolToken,
        uint256 _amount
    )
        external
    {
        allowance[msg.sender][_poolToken][_spender] = _amount;

        emit Approve(
            _spender,
            _poolToken,
            msg.sender,
            _amount,
            block.timestamp
        );
    }

    /**
     * @dev Enables _poolToken to be used as a collateral.
     */
    function collateralizeDeposit(
        uint256 _nftId,
        address _poolToken
    )
        external
    {
        WISE_SECURITY.checksCollateralizeDeposit(
            _nftId,
            msg.sender,
            _poolToken
        );

        _updateCollateralize({
            _nftId: _nftId,
            _poolToken: _poolToken,
            _caller: msg.sender,
            _allowAsCollateral: true
        });
    }

    /**
     * @dev Disables _poolToken to be used as a collateral.
     */
    function decollateralizeDeposit(
        uint256 _nftId,
        address _poolToken
    )
        external
        syncPool(_poolToken)
    {
        WISE_SECURITY.checkOwnerPosition(
            _nftId,
            msg.sender
        );

        _prepareAssociatedTokens(
            _nftId,
            _poolToken
        );

        _updateCollateralize({
            _nftId: _nftId,
            _poolToken: _poolToken,
            _caller: msg.sender,
            _allowAsCollateral: false
        });

        WISE_SECURITY.checksDecollateralizeDeposit(
            _nftId,
            _poolToken
        );
    }

    // --------------- Deposit Functions -------------

    /**
     * @dev Allows to supply funds using ETH.
     * Without converting to WETH, use ETH directly.
     */
    function depositExactAmountETH(
        uint256 _nftId,
        bool _collateralState
    )
        public
        payable
        syncPool(WETH_ADDRESS)
        returns (uint256)
    {
        uint256 shareAmount = calculateLendingShares(
            WETH_ADDRESS,
            msg.value
        );

        _handleDeposit(
            {
                _caller: msg.sender,
                _nftId: _nftId,
                _poolToken: WETH_ADDRESS,
                _amount: msg.value,
                _shareAmount: shareAmount,
                _state: _collateralState
            }
        );

        _wrapETH(
            msg.value
        );

        return shareAmount;
    }

    /**
     * @dev Allows to supply funds using ETH.
     * Without converting to WETH, use ETH directly,
     * also mints position to avoid extra transaction.
     */
    function depositExactAmountETHMint(
        bool _collateralState
    )
        external
        payable
        returns (uint256)
    {
        uint256 shareAmount = depositExactAmountETH(
            _mintPosition(),
            _collateralState
        );

        return shareAmount;
    }

    /**
     * @dev Allows to supply _poolToken and user
     * can decide if _poolToken should be collateralized,
     * also mints position to avoid extra transaction.
     */
    function depositExactAmountMint(
        address _poolToken,
        uint256 _amount,
        bool _collateralState
    )
        external
        returns (uint256)
    {
        uint256 shares = depositExactAmount(
            _mintPosition(),
            _poolToken,
            _amount,
            _collateralState
        );

        return shares;
    }

    /**
     * @dev Allows to supply _poolToken and user
     * can decide if _poolToken should be collateralized.
     */
    function depositExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        bool _collateralState
    )
        public
        syncPool(_poolToken)
        returns (uint256)
    {
        uint256 shareAmount = calculateLendingShares(
            _poolToken,
            _amount
        );

        _handleDeposit(
            {
                _caller: msg.sender,
                _nftId: _nftId,
                _poolToken: _poolToken,
                _amount: _amount,
                _shareAmount: shareAmount,
                _state: _collateralState
            }
        );

        _safeTransferFrom(
            _poolToken,
            msg.sender,
            address(this),
            _amount
        );

        return shareAmount;
    }

    /**
     * @dev Allows to supply funds using ETH in solely mode,
     * which does not earn APY, but keeps the funds private.
     * Other users are restricted from borrowing these funds,
     * owner can always withdraw even if all funds are borrowed.
     * Also mints position to avoid extra transaction.
     */
    function solelyDepositETHMint()
        external
        payable
    {
        solelyDepositETH(
            _mintPosition()
        );
    }

    /**
     * @dev Allows to supply funds using ETH in solely mode,
     * which does not earn APY, but keeps the funds private.
     * Other users are restricted from borrowing these funds,
     * owner can always withdraw even if all funds are borrowed.
     */
    function solelyDepositETH(
        uint256 _nftId
    )
        public
        payable
        syncPool(WETH_ADDRESS)
    {
        _handleSolelyDeposit(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            msg.value
        );

        _wrapETH(
            msg.value
        );

        emit FundsSolelyDeposited(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            msg.value,
            block.timestamp
        );
    }

    /**
     * @dev Allows to supply funds using ERC20 in solely mode,
     * which does not earn APY, but keeps the funds private.
     * Other users are restricted from borrowing these funds,
     * owner can always withdraw even if all funds are borrowed.
     * Also mints position to avoid extra transaction.
     */
    function solelyDepositMint(
        address _poolToken,
        uint256 _amount
    )
        external
    {
        solelyDeposit(
            _mintPosition(),
            _poolToken,
            _amount
        );
    }

    /**
     * @dev Allows to supply funds using ERC20 in solely mode,
     * which does not earn APY, but keeps the funds private.
     * Other users are restricted from borrowing these funds,
     * owner can always withdraw even if all funds are borrowed.
     */
    function solelyDeposit(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        public
        syncPool(_poolToken)
    {
        _handleSolelyDeposit(
            msg.sender,
            _nftId,
            _poolToken,
            _amount
        );

        _safeTransferFrom(
            _poolToken,
            msg.sender,
            address(this),
            _amount
        );

        emit FundsSolelyDeposited(
            msg.sender,
            _nftId,
            _poolToken,
            _amount,
            block.timestamp
        );
    }

    // --------------- Withdraw Functions -------------

    /**
     * @dev Allows to withdraw publicly
     * deposited ETH funds using exact amount.
     */
    function withdrawExactAmountETH(
        uint256 _nftId,
        uint256 _amount
    )
        external
        syncPool(WETH_ADDRESS)
        returns (uint256)
    {
        uint256 withdrawShares = _preparationsWithdraw(
            _nftId,
            msg.sender,
            WETH_ADDRESS,
            _amount
        );

        _coreWithdrawToken(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            _amount,
            withdrawShares
        );

        _unwrapETH(
            _amount
        );

        payable(msg.sender).transfer(
            _amount
        );

        emit FundsWithdrawn(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            _amount,
            withdrawShares,
            block.timestamp
        );

        return withdrawShares;
    }

    /**
     * @dev Allows to withdraw publicly
     * deposited ETH funds using exact shares.
     */
    function withdrawExactSharesETH(
        uint256 _nftId,
        uint256 _shares
    )
        external
        syncPool(WETH_ADDRESS)
        returns (uint256)
    {
        WISE_SECURITY.checkOwnerPosition(
            _nftId,
            msg.sender
        );

        uint256 withdrawAmount = cashoutAmount(
            WETH_ADDRESS,
            _shares
        );

        _coreWithdrawToken(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            withdrawAmount,
            _shares
        );

        _unwrapETH(
            withdrawAmount
        );

        payable(msg.sender).transfer(
            withdrawAmount
        );

        emit FundsWithdrawn(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            withdrawAmount,
            _shares,
            block.timestamp
        );

        return withdrawAmount;
    }

    /**
     * @dev Allows to withdraw publicly
     * deposited ERC20 funds using exact amount.
     */
    function withdrawExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _withdrawAmount
    )
        external
        syncPool(_poolToken)
        returns (uint256)
    {
        uint256 withdrawShares = _preparationsWithdraw(
            _nftId,
            msg.sender,
            _poolToken,
            _withdrawAmount
        );

        _coreWithdrawToken(
            msg.sender,
            _nftId,
            _poolToken,
            _withdrawAmount,
            withdrawShares
        );

        _safeTransfer(
            _poolToken,
            msg.sender,
            _withdrawAmount
        );

        emit FundsWithdrawn(
            msg.sender,
            _nftId,
            _poolToken,
            _withdrawAmount,
            withdrawShares,
            block.timestamp
        );

        return withdrawShares;
    }

    /**
     * @dev Allows to withdraw privately
     * deposited ETH funds using input amount.
     */
    function solelyWithdrawETH(
        uint256 _nftId,
        uint256 withdrawAmount
    )
        external
        syncPool(WETH_ADDRESS)
    {
        WISE_SECURITY.checkOwnerPosition(
            _nftId,
            msg.sender
        );

        _coreSolelyWithdraw(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            withdrawAmount
        );

        _unwrapETH(
            withdrawAmount
        );

        payable(msg.sender).transfer(
            withdrawAmount
        );

        emit FundsSolelyWithdrawn(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            withdrawAmount,
            block.timestamp
        );
    }

    /**
     * @dev Allows to withdraw privately
     * deposited ERC20 funds using input amount.
     */
    function solelyWithdraw(
        uint256 _nftId,
        address _poolToken,
        uint256 _withdrawAmount
    )
        external
        syncPool(_poolToken)
    {
        WISE_SECURITY.checkOwnerPosition(
            _nftId,
            msg.sender
        );

        _coreSolelyWithdraw(
            msg.sender,
            _nftId,
            _poolToken,
            _withdrawAmount
        );

        _safeTransfer(
            _poolToken,
            msg.sender,
            _withdrawAmount
        );

        emit FundsSolelyWithdrawn(
            msg.sender,
            _nftId,
            _poolToken,
            _withdrawAmount,
            block.timestamp
        );
    }

    /**
     * @dev Allows to withdraw privately
     * deposited ERC20 on behalf of owner.
     * Requires approval by _nftId owner.
     */
    function solelyWithdrawOnBehalf(
        uint256 _nftId,
        address _poolToken,
        uint256 _withdrawAmount
    )
        external
        syncPool(_poolToken)
    {
        _reduceAllowance(
            _nftId,
            _poolToken,
            msg.sender,
            _withdrawAmount
        );

        _coreSolelyWithdraw(
            msg.sender,
            _nftId,
            _poolToken,
            _withdrawAmount
        );

        _safeTransfer(
            _poolToken,
            msg.sender,
            _withdrawAmount
        );

        emit FundsSolelyWithdrawnOnBehalf(
            msg.sender,
            _nftId,
            _poolToken,
            _withdrawAmount,
            block.timestamp
        );
    }

    /**
     * @dev Allows to withdraw privately
     * deposited ERC20 on behalf of owner.
     * Requires approval by _nftId owner.
     */
    function withdrawOnBehalfExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _withdrawAmount
    )
        external
        syncPool(_poolToken)
        returns (uint256)
    {
        _reduceAllowance(
            _nftId,
            _poolToken,
            msg.sender,
            _withdrawAmount
        );

        uint256 withdrawShares = calculateLendingShares(
            _poolToken,
            _withdrawAmount
        );

        _coreWithdrawToken(
            msg.sender,
            _nftId,
            _poolToken,
            _withdrawAmount,
            withdrawShares
        );

        _safeTransfer(
            _poolToken,
            msg.sender,
            _withdrawAmount
        );

        emit FundsWithdrawnOnBehalf(
            msg.sender,
            _nftId,
            _poolToken,
            _withdrawAmount,
            withdrawShares,
            block.timestamp
        );

        return withdrawShares;
    }

    /**
     * @dev Allows to withdraw ERC20
     * funds using shares as input value
     */
    function withdrawExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        syncPool(_poolToken)
        returns (uint256)
    {
        WISE_SECURITY.checkOwnerPosition(
            _nftId,
            msg.sender
        );

        uint256 withdrawAmount = cashoutAmount(
            _poolToken,
            _shares
        );

        _coreWithdrawToken(
            msg.sender,
            _nftId,
            _poolToken,
            withdrawAmount,
            _shares
        );

        _safeTransfer(
            _poolToken,
            msg.sender,
            withdrawAmount
        );

        emit FundsWithdrawn(
            msg.sender,
            _nftId,
            _poolToken,
            withdrawAmount,
            _shares,
            block.timestamp
        );

        return withdrawAmount;
    }

    /**
     * @dev Withdraws ERC20 funds on behalf
     * of _nftId owner, requires approval.
     */
    function withdrawOnBehalfExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        syncPool(_poolToken)
        returns (uint256)
    {
        uint256 withdrawAmount = cashoutAmount(
            _poolToken,
            _shares
        );

        _reduceAllowance(
            _nftId,
            _poolToken,
            msg.sender,
            withdrawAmount
        );

        _coreWithdrawToken(
            msg.sender,
            _nftId,
            _poolToken,
            withdrawAmount,
            _shares
        );

        _safeTransfer(
            _poolToken,
            msg.sender,
            withdrawAmount
        );

        emit FundsWithdrawnOnBehalf(
            msg.sender,
            _nftId,
            _poolToken,
            withdrawAmount,
            _shares,
            block.timestamp
        );

        return withdrawAmount;
    }

    // --------------- Borrow Functions -------------

    /**
     * @dev Allows to borrow ETH funds
     * Requires user to have collateral.
     */
    function borrowExactAmountETH(
        uint256 _nftId,
        uint256 _amount
    )
        external
        syncPool(WETH_ADDRESS)
        returns (uint256)
    {
        WISE_SECURITY.checkOwnerPosition(
            _nftId,
            msg.sender
        );

        uint256 shares = calculateBorrowShares(
            WETH_ADDRESS,
            _amount
        );

        _coreBorrowTokens(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            _amount,
            shares
        );

        _unwrapETH(
            _amount
        );

        payable(msg.sender).transfer(
            _amount
        );

        emit FundsBorrowed(
            msg.sender,
            _nftId,
            WETH_ADDRESS,
            _amount,
            shares,
            block.timestamp
        );

        return shares;
    }

    /**
     * @dev Allows to borrow ERC20 funds
     * Requires user to have collateral.
     */
    function borrowExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        syncPool(_poolToken)
        returns (uint256)
    {
        WISE_SECURITY.checkOwnerPosition(
            _nftId,
            msg.sender
        );

        uint256 shares = calculateBorrowShares(
            _poolToken,
            _amount
        );

        _coreBorrowTokens(
            msg.sender,
            _nftId,
            _poolToken,
            _amount,
            shares
        );

        _safeTransfer(
            _poolToken,
            msg.sender,
            _amount
        );

        emit FundsBorrowed(
            msg.sender,
            _nftId,
            _poolToken,
            _amount,
            shares,
            block.timestamp
        );

        return shares;
    }

    /**
     * @dev Allows to borrow ERC20 funds
     * on behalf of _nftId owner, if approved.
     */
    function borrowOnBehalfExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        syncPool(_poolToken)
        returns (uint256)
    {
        _reduceAllowance(
            _nftId,
            _poolToken,
            msg.sender,
            _amount
        );

        uint256 shares = calculateBorrowShares(
            _poolToken,
            _amount
        );

        _coreBorrowTokens(
            msg.sender,
            _nftId,
            _poolToken,
            _amount,
            shares
        );

        _safeTransfer(
            _poolToken,
            msg.sender,
            _amount
        );

        emit FundsBorrowedOnBehalf(
            msg.sender,
            _nftId,
            _poolToken,
            _amount,
            shares,
            block.timestamp
        );

        return shares;
    }

    // --------------- Payback Functions ------------

    /**
     * @dev Ability to payback ETH loans
     * by providing exact payback amount.
     */
    function paybackExactAmountETH(
        uint256 _nftId
    )
        external
        payable
        syncPool(WETH_ADDRESS)
        returns (uint256)
    {
        WISE_SECURITY.checkPositionLocked(
            _nftId,
            msg.sender
        );

        uint256 maxBorrowShares = getPositionBorrowShares(
            _nftId,
            WETH_ADDRESS
        );

        uint256 maxPaybackAmount = paybackAmount(
            WETH_ADDRESS,
            maxBorrowShares
        );

        uint256 paybackShares = calculateBorrowShares(
            WETH_ADDRESS,
            msg.value
        );

        uint256 refundAmount;
        uint256 requiredAmount = msg.value;

        if (msg.value > maxPaybackAmount) {

            refundAmount = msg.value
                - maxPaybackAmount;

            requiredAmount = requiredAmount
                - refundAmount;

            paybackShares = maxBorrowShares;
        }

        _handlePayback(
            {
                _caller: msg.sender,
                _nftId: _nftId,
                _poolToken: WETH_ADDRESS,
                _amount: requiredAmount,
                _shares: paybackShares
            }
        );

        _wrapETH(
            requiredAmount
        );

        if (refundAmount > 0) {
            payable(msg.sender).transfer(
                refundAmount
            );
        }

        return paybackShares;
    }

    /**
     * @dev Ability to payback ERC20 loans
     * by providing exact payback amount.
     */
    function paybackExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        public
        syncPool(_poolToken)
        returns (uint256)
    {
        WISE_SECURITY.checkPositionLocked(
            _nftId,
            msg.sender
        );

        uint256 paybackShares = calculateBorrowShares(
            _poolToken,
            _amount
        );

        _handlePayback(
            {
                _caller: msg.sender,
                _nftId: _nftId,
                _poolToken: _poolToken,
                _amount: _amount,
                _shares: paybackShares
            }
        );

        _safeTransferFrom(
            _poolToken,
            msg.sender,
            address(this),
            _amount
        );

        return paybackShares;
    }

    /**
     * @dev Ability to payback ERC20 loans
     * by providing exact payback shares.
     */
    function paybackExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        public
        syncPool(_poolToken)
        returns (uint256)
    {
        WISE_SECURITY.checkPositionLocked(
            _nftId,
            msg.sender
        );

        uint256 paybackAmount = paybackAmount(
            _poolToken,
            _shares
        );

        _handlePayback(
            {
                _caller: msg.sender,
                _nftId: _nftId,
                _poolToken: _poolToken,
                _amount: paybackAmount,
                _shares: _shares
            }
        );

        _safeTransferFrom(
            _poolToken,
            msg.sender,
            address(this),
            paybackAmount
        );

        return paybackAmount;
    }

    /**
     * @dev Ability to payback ERC20 loans
     * by providing exact lending shares.
     */
    function paybackExactLendingShares(
        uint256 _nftIdCaller,
        uint256 _nftIdReceiver,
        address _poolToken,
        uint256 _lendingShares
    )
        public
        syncPool(_poolToken)
        returns (uint256)
    {
        _prepareAssociatedTokens(
            _nftIdCaller,
            _poolToken
        );

        uint256 paybackAmount = cashoutAmount(
            _poolToken,
            _lendingShares
        );

        WISE_SECURITY.checkPaybackLendingShares(
            _nftIdReceiver,
            _nftIdCaller,
            msg.sender,
            _poolToken,
            paybackAmount
        );

        _corePaybackLendingShares(
            _poolToken,
            paybackAmount,
            _lendingShares,
            _nftIdCaller,
            _nftIdReceiver
        );

        emit FundsReturnedWithLendingShares(
            msg.sender,
            _poolToken,
            _nftIdCaller,
            paybackAmount,
            _lendingShares,
            block.timestamp
        );

        if (getPositionBorrowShares(_nftIdReceiver, _poolToken) > 0) {
            return paybackAmount;
        }

        _removePositionData({
            _nftId: _nftIdReceiver,
            _poolToken: _poolToken,
            _getPositionTokenLength: getPositionBorrowTokenLength,
            _getPositionTokenByIndex: getPositionBorrowTokenByIndex,
            _deleteLastPositionData: _deleteLastPositionBorrowData,
            isLending: false
        });

        return paybackAmount;
    }

    /**
     * @dev Allows to sync pool manually
     * so that the pool is up to date.
     */
    function syncManually(
        address _poolToken
    )
        external
        syncPool(_poolToken)
    {
        emit PoolSynced(
            _poolToken,
            block.timestamp
        );
    }

    /**
     * @dev Registers position _nftId
     * for isolation pool functionality
     */
    function setRegistrationIsolationPool(
        uint256 _nftId,
        bool _registerState,
        address _isolationPool
    )
        external
    {
        WISE_SECURITY.onlyIsolationPool(
            msg.sender
        );

        _setIsolationPoolMappings(
            _nftId,
            _isolationPool,
            _registerState
        );
    }

    /**
     * @dev Allow to verify isolation pool.
     */
    function setVeryfiedIsolationPool(
        address _isolationPool,
        bool _state
    )
        external
        onlyMaster
    {
        veryfiedIsolationPool[_isolationPool] = _state;
    }
}

// File: contracts/PoolManager.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

import "./WiseCore.sol";
import "./Babylonian.sol";

abstract contract PoolManager is WiseCore {

    struct CreatePoolStruct {
        bool allowBorrow;
        address poolToken;
        address curvePool;
        address curveMetaPool;
        address[] underlyingPoolTokens;
        CurveSwapStruct curveSecuritySwaps;
        uint256 poolMulFactor;
        uint256 poolCollFactor;
        uint256 maxDepositAmount;
    }

    function setParamsLASA(
        address _poolToken,
        uint256 _poolMulFactor,
        uint256 _upperBoundMaxRate,
        uint256 _lowerBoundMaxRate,
        bool _steppingDirection,
        bool _isFinal
    )
        external
        onlyMaster
    {
        if (parametersLocked[_poolToken] == true) {
            revert ParametersLocked();
        }

        parametersLocked[_poolToken] = _isFinal;
        algorithmData[_poolToken].increasePole = _steppingDirection;

        uint256 staticMinPole = _getMinPole(
            _poolMulFactor,
            _upperBoundMaxRate
        );

        uint256 staticMaxPole = _getMaxPole(
            _poolMulFactor,
            _lowerBoundMaxRate
        );

        uint256 staticDeltaPole = _getDeltaPole(
            staticMaxPole,
            staticMinPole
        );

        uint256 startValuePole = _getStartValue(
            staticMaxPole,
            staticMinPole
        );

        borrowRatesData[_poolToken] = BorrowRatesEntry(
            {
                pole: startValuePole,
                deltaPole: staticDeltaPole,
                minPole: staticMinPole,
                maxPole: staticMaxPole,
                multiplicativeFactor: _poolMulFactor
            }
        );

        algorithmData[_poolToken].bestPole = startValuePole;
        algorithmData[_poolToken].maxValue = lendingPoolData[_poolToken].totalDepositShares;
    }

    function setPoolParameters(
        address _poolToken,
        uint256 _collateralFactor,
        uint256 _maximumDeposit
    )
        external
        onlyMaster
    {
        if (_maximumDeposit > 0) {
            maxDepositValueToken[_poolToken] = _maximumDeposit;
        }

        if (_collateralFactor > 0) {
            lendingPoolData[_poolToken].collateralFactor = _collateralFactor;
        }

        if (_collateralFactor > PRECISION_FACTOR_E18) {
            revert ForbiddenValue();
        }
    }

    function createPool(
        CreatePoolStruct memory _params
    )
        external
        onlyMaster
    {
        if (timestampsPoolData[_params.poolToken].timeStamp > 0) {
            revert AlreadyCreated();
        }

        // Default boundary values for pool creation.
        uint256 LOWER_BOUND_MAX_RATE = 100 * PRECISION_FACTOR_E16;
        uint256 UPPER_BOUND_MAX_RATE = 300 * PRECISION_FACTOR_E16;

        // Calculating lower bound for the pole
        uint256 staticMinPole = _getMinPole(
            _params.poolMulFactor,
            UPPER_BOUND_MAX_RATE
        );

        // Calculating upper bound for the pole
        uint256 staticMaxPole = _getMaxPole(
            _params.poolMulFactor,
            LOWER_BOUND_MAX_RATE
        );

        // Calculating fraction for algorithm step
        uint256 staticDeltaPole = _getDeltaPole(
            staticMaxPole,
            staticMinPole
        );

        uint256 timeNow = block.timestamp;
        maxDepositValueToken[_params.poolToken] = _params.maxDepositAmount;

        FEE_MANAGER.addPoolTokenAddress(
            _params.poolToken
        );

        globalPoolData[_params.poolToken] = GlobalPoolEntry({
            totalPool: 0,
            utilization: 0,
            totalBareToken: 0,
            poolFee: 20 * PRECISION_FACTOR_E16
        });

        // Setting start value as mean of min and max value
        uint256 startValuePole = _getStartValue(
            staticMaxPole,
            staticMinPole
        );

        // Rates Pool Data
        borrowRatesData[_params.poolToken] = BorrowRatesEntry({
            pole: startValuePole,
            deltaPole: staticDeltaPole,
            minPole: staticMinPole,
            maxPole: staticMaxPole,
            multiplicativeFactor: _params.poolMulFactor
        });

        // Borrow Pool Data
        borrowPoolData[_params.poolToken] = BorrowPoolEntry({
            allowBorrow: _params.allowBorrow,
            pseudoTotalBorrowAmount: 1,
            totalBorrowShares: 1,
            borrowRate: 0
        });

        // Algorithm Pool Data
        algorithmData[_params.poolToken] = AlgorithmEntry({
            bestPole: startValuePole,
            maxValue: 0,
            previousValue: 0,
            increasePole: false
        });

        _prepareNewPool(
            IERC20(_params.poolToken)
        );

        // Lending Pool Data
        lendingPoolData[_params.poolToken] = LendingPoolEntry({
            pseudoTotalPool: 1,
            totalDepositShares: 1,
            collateralFactor: _params.poolCollFactor
        });

        // Timestamp Pool Data
        timestampsPoolData[_params.poolToken] = TimestampsPoolEntry({
            timeStamp: timeNow,
            timeStampScaling: timeNow
        });

        IERC20(_params.poolToken).approve(
            address(WISE_LIQUIDATION),
            0
        );

        IERC20(_params.poolToken).approve(
            address(WISE_LIQUIDATION),
            UINT256_MAX
        );

        if (_params.curvePool > ZERO_ADDRESS) {
            _prepareCurvePools(
                _params.poolToken,
                _params.curvePool,
                _params.curveMetaPool,
                _params.underlyingPoolTokens,
                _params.curveSecuritySwaps
            );
        }
    }

    function _prepareCurvePools(
        address _poolToken,
        address _curvePool,
        address _curveMetaPool,
        address[] memory _underlyingPoolTokens,
        CurveSwapStruct memory _curveSwapStruct
    )
        internal
    {
        WISE_SECURITY.prepareCurvePools(
            _poolToken,
            _curvePool,
            _curveMetaPool,
            _curveSwapStruct
        );

        WISE_SECURITY.setUnderlyingPoolTokensFromPoolToken(
            _poolToken,
            _underlyingPoolTokens
        );
    }

    function _prepareNewPool(
        IERC20 _poolToken
    )
        internal
        returns (uint256)
    {
        uint256 fetchBalance = _poolToken.balanceOf(
            address(this)
        );

        if (fetchBalance == 0) {
            return 0;
        }

        _poolToken.transfer(
            master,
            fetchBalance
        );

        return fetchBalance;
    }

    function _getMaxPole(
        uint256 _poolMulFactor,
        uint256 _lowerBoundMaxRate
    )
        private
        pure
        returns (uint256)
    {
        return PRECISION_FACTOR_E18 / 2
            + Babylonian.sqrt(PRECISION_FACTOR_E36 / 4
                + _poolMulFactor
                    * PRECISION_FACTOR_E36
                    / _lowerBoundMaxRate
            );
    }

    function _getMinPole(
        uint256 _poolMulFactor,
        uint256 _upperBoundMaxRate
    )
        private
        pure
        returns (uint256)
    {
        return PRECISION_FACTOR_E18 / 2
            + Babylonian.sqrt(PRECISION_FACTOR_E36 / 4
                + _poolMulFactor
                    * PRECISION_FACTOR_E36
                    / _upperBoundMaxRate
            );
    }

    function _getDeltaPole(
        uint256 _maxPole,
        uint256 _minPole
    )
        private
        pure
        returns (uint256)
    {
        return (_maxPole - _minPole)
            / NORMALISATION_FACTOR;
    }

    function _getStartValue(
        uint256 _maxPole,
        uint256 _minPole
    )
        private
        pure
        returns (uint256)
    {
        return (_maxPole + _minPole)
            / 2;
    }
}

// File: contracts/Babylonian.sol



// SPDX-License-Identifier: WISE

pragma solidity =0.8.21;

library Babylonian {

    function sqrt(
        uint256 x
    )
        internal
        pure
        returns (uint256)
    {
        if (x == 0) return 0;

        uint256 xx = x;
        uint256 r = 1;
        if (xx >= 0x100000000000000000000000000000000) {
            xx >>= 128;
            r <<= 64;
        }
        if (xx >= 0x10000000000000000) {
            xx >>= 64;
            r <<= 32;
        }
        if (xx >= 0x100000000) {
            xx >>= 32;
            r <<= 16;
        }
        if (xx >= 0x10000) {
            xx >>= 16;
            r <<= 8;
        }
        if (xx >= 0x100) {
            xx >>= 8;
            r <<= 4;
        }
        if (xx >= 0x10) {
            xx >>= 4;
            r <<= 2;
        }
        if (xx >= 0x8) {
            r <<= 1;
        }
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;
        r = (r + x / r) >> 1;

        uint256 r1 = x / r;
        return (r < r1 ? r : r1);
    }
}

// File: contracts/WiseCore.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

import "./MainHelper.sol";

abstract contract WiseCore is MainHelper {

    function _prepareAssociatedTokens(
        uint256 _nftId,
        address _poolToken
    )
        internal
    {
        if (veryfiedIsolationPool[msg.sender] == true) {
            return;
        }

        if (msg.sender == AAVE_HUB) {
            return;
        }

        _preparationCollaterals(
            _nftId,
            _poolToken
        );

        _preparationBorrows(
            _nftId,
            _poolToken
        );
    }

    function _coreDepositTokens(
        address _caller,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        uint256 _share
    )
        internal
    {
        WISE_SECURITY.checksDeposit(
            _nftId,
            _caller,
            _poolToken,
            _amount
        );

        _updatePositionLendingDeposit(
            _nftId,
            _poolToken,
            _share
        );

        _updatePoolStorage(
            _poolToken,
            _amount,
            _share,
            _increaseTotalPool,
            _increasePseudoTotalPool,
            _increaseTotalDepositShares
        );

        _addPositionTokenData(
            _nftId,
            _poolToken,
            hashMapPositionLending,
            positionLendingTokenData
        );
    }

    function _coreWithdrawToken(
        address _caller,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        uint256 _shares
    )
        internal
    {
        _prepareAssociatedTokens(
            _nftId,
            _poolToken
        );

        WISE_SECURITY.checksWithdraw(
            _nftId,
            _caller,
            _poolToken,
            _amount
        );

        _coreWithdrawBare(
            _nftId,
            _poolToken,
            _amount,
            _shares
        );
    }

    function _handlePayback(
        address _caller,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        uint256 _shares
    )
        internal
    {
        _corePayback(
            _nftId,
            _poolToken,
            _amount,
            _shares
        );

        emit FundsReturned(
            _caller,
            _poolToken,
            _nftId,
            _amount,
            _shares,
            block.timestamp
        );
    }

    function _handleDeposit(
        address _caller,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        uint256 _shareAmount,
        bool _state
    )
        internal
    {
        _coreDepositTokens(
            _caller,
            _nftId,
            _poolToken,
            _amount,
            _shareAmount
        );

        if (userLendingData[_nftId][_poolToken].collaterized == false) {
            userLendingData[_nftId][_poolToken].collaterized = _state;
        }

        emit FundsDeposited(
            _caller,
            _nftId,
            _poolToken,
            _amount,
            _shareAmount,
            block.timestamp
        );
    }

    function _handleSolelyDeposit(
        address _caller,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        WISE_SECURITY.checksDeposit(
            _nftId,
            _caller,
            _poolToken,
            _amount
        );

        _increaseMappingValue(
            positionPureCollateralAmount,
            _nftId,
            _poolToken,
            _amount
        );

        _increaseTotalBareToken(
            _poolToken,
            _amount
        );

        _addPositionTokenData(
            _nftId,
            _poolToken,
            hashMapPositionLending,
            positionLendingTokenData
        );
    }

    function liquidationCoreWithdraw(
        address _poolToken,
        uint256 _nftId,
        uint256 _amount,
        uint256 _shares
    )
        external
        onlyLiquidation
    {
        _coreWithdrawBare(
            _nftId,
            _poolToken,
            _amount,
            _shares
        );

        emit FundsWithdrawn(
            msg.sender,
            _nftId,
            _poolToken,
            _amount,
            _shares,
            block.timestamp
        );
    }

    function _coreWithdrawBare(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        uint256 _shares
    )
        internal
    {
        _updatePoolStorage(
            _poolToken,
            _amount,
            _shares,
            _decreaseTotalPool,
            _decreasePseudoTotalPool,
            _decreaseTotalDepositShares
        );

        _decreaseLendingShares(
            _nftId,
            _poolToken,
            _shares
        );

        _removeEmptyLendingData(
            _nftId,
            _poolToken
        );
    }

    function _coreBorrowTokens(
        address _caller,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        uint256 _shares
    )
        internal
    {
        _prepareAssociatedTokens(
            _nftId,
            _poolToken
        );

        WISE_SECURITY.checksBorrow(
            _nftId,
            _caller,
            _poolToken,
            _amount
        );

        _updatePoolStorage(
            _poolToken,
            _amount,
            _shares,
            _increasePseudoTotalBorrowAmount,
            _decreaseTotalPool,
            _increaseTotalBorrowShares
        );

        _increaseMappingValue(
            userBorrowShares,
            _nftId,
            _poolToken,
            _shares
        );

        _addPositionTokenData(
            _nftId,
            _poolToken,
            hashMapPositionBorrow,
            positionBorrowTokenData
        );
    }

    function _corePayback(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        uint256 _shares
    )
        internal
    {
        _updatePoolStorage(
            _poolToken,
            _amount,
            _shares,
            _increaseTotalPool,
            _decreasePseudoTotalBorrowAmount,
            _decreaseTotalBorrowShares
        );

        _decreasePositionMappingValue(
            userBorrowShares,
            _nftId,
            _poolToken,
            _shares
        );

        if (getPositionBorrowShares(_nftId, _poolToken) > 0) {
            return;
        }

        _removePositionData({
            _nftId: _nftId,
            _poolToken: _poolToken,
            _getPositionTokenLength: getPositionBorrowTokenLength,
            _getPositionTokenByIndex: getPositionBorrowTokenByIndex,
            _deleteLastPositionData: _deleteLastPositionBorrowData,
            isLending: false
        });
    }

    function liquidationCorePayback(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount,
        uint256 _shares
    )
        external
        onlyLiquidation
    {
        _corePayback(
            _nftId,
            _poolToken,
            _amount,
            _shares
        );
    }

    function corePaybackFeeManager(
        address _poolToken,
        uint256 _nftId,
        uint256 _amount,
        uint256 _shares
    )
        external
        onlyFeeManager
    {
        _corePayback(
            _nftId,
            _poolToken,
            _amount,
            _shares
        );
    }

    function _coreSolelyWithdraw(
        address _caller,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        WISE_SECURITY.checksSolelyWithdraw(
            _nftId,
            _caller,
            _poolToken,
            _amount
        );

        _solelyWithdrawBase(
            _poolToken,
            _nftId,
            _amount
        );
    }

    function _solelyWithdrawBase(
        address _poolToken,
        uint256 _nftId,
        uint256 _amount
    )
        internal
    {
        _decreasePositionMappingValue(
            positionPureCollateralAmount,
            _nftId,
            _poolToken,
            _amount
        );

        _decreaseTotalBareToken(
            _poolToken,
            _amount
        );

        _removeEmptyLendingData(
            _nftId,
            _poolToken
        );
    }

    function _corePaybackLendingShares(
        address _poolToken,
        uint256 _tokenAmount,
        uint256 _lendingShares,
        uint256 _nftIdCaller,
        uint256 _nftIdReceiver
    )
        internal
    {
        uint256 borrowShareEquivalent = _borrowShareEquivalent(
            _poolToken,
            _lendingShares
        );

        _updatePoolStorage(
            _poolToken,
            _tokenAmount,
            _lendingShares,
            _decreasePseudoTotalPool,
            _decreasePseudoTotalBorrowAmount,
            _decreaseTotalDepositShares
        );

        _decreaseLendingShares(
            _nftIdCaller,
            _poolToken,
            _lendingShares
        );

        _decreaseTotalBorrowShares(
            _poolToken,
            borrowShareEquivalent
        );

        _decreasePositionMappingValue(
            userBorrowShares,
            _nftIdReceiver,
            _poolToken,
            borrowShareEquivalent
        );
    }
}

// File: contracts/MainHelper.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

import "./WiseLowLevelHelper.sol";
import "./TransferHub/TransferHelper.sol";

abstract contract MainHelper is WiseLowLevelHelper, TransferHelper {

    function _mintPosition()
        internal
        returns (uint256)
    {
        return POSITION_NFT.mintPositionForUser(
            msg.sender
        );
    }

    function calculateLendingShares(
        address _poolToken,
        uint256 _amount
    )
        public
        view
        returns (uint256)
    {
        uint256 shares = getTotalDepositShares(
            _poolToken
        );

        uint256 pseudo = getPseudoTotalPool(
            _poolToken
        );

        if (shares <= 1) {
            return _amount;
        }

        if (pseudo == 0) {
            return _amount;
        }

        return _amount
            * shares
            / pseudo;
    }

    function calculateBorrowShares(
        address _poolToken,
        uint256 _amount
    )
        public
        view
        returns (uint256)
    {
        uint256 shares = getTotalBorrowShares(
            _poolToken
        );

        uint256 pseudo = getPseudoTotalBorrowAmount(
            _poolToken
        );

        if (shares <= 1) {
            return _amount;
        }

        if (pseudo == 0) {
            return _amount;
        }

        return _amount
            * shares
            / pseudo;
    }

    function cashoutAmount(
        address _poolToken,
        uint256 _shares
    )
        public
        view
        returns (uint256)
    {
        return _shares
            * getPseudoTotalPool(_poolToken)
            / getTotalDepositShares(_poolToken);
    }

    function paybackAmount(
        address _poolToken,
        uint256 _shares
    )
        public
        view
        returns (uint256)
    {
        return _shares
            * getPseudoTotalBorrowAmount(_poolToken)
            / getTotalBorrowShares(_poolToken);
    }

    function _preparationsWithdraw(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        internal
        view
        returns (uint256)
    {
        WISE_SECURITY.checkOwnerPosition(
            _nftId,
            _caller
        );

        return calculateLendingShares(
            _poolToken,
            _amount
        );
    }

    function _getValueUtilization(
        address _poolToken
    )
        private
        view
        returns (uint256)
    {
        if (getTotalPool(_poolToken) >= getPseudoTotalPool(_poolToken)) {
            return 0;
        }

        return PRECISION_FACTOR_E18 - (PRECISION_FACTOR_E18
            * getTotalPool(_poolToken)
            / getPseudoTotalPool(_poolToken)
        );
    }

    function _updateUtilization(
        address _poolToken
    )
        private
    {
        globalPoolData[_poolToken].utilization = _getValueUtilization(
            _poolToken
        );
    }

    function _checkCleanUp(
        uint256 _amountContract,
        uint256 _totalPool,
        uint256 _bareAmount
    )
        private
        pure
        returns (bool)
    {
        return _bareAmount + _totalPool >= _amountContract;
    }

    function _cleanUp(
        address _poolToken
    )
        internal
    {
        uint256 amountContract = IERC20(_poolToken).balanceOf(
            address(this)
        );

        uint256 totalPool = getTotalPool(
            _poolToken
        );

        uint256 bareToken = getTotalBareToken(
            _poolToken
        );

        if (_checkCleanUp(amountContract, totalPool, bareToken)) {
            return;
        }

        uint256 diff = amountContract - (
            totalPool + bareToken
        );

        _increaseTotalAndPseudoTotalPool(
            _poolToken,
            diff
        );
    }

    function preparePool(
        address _poolToken
    )
        external
        onlyAllowedContracts
    {
        _preparePool(
            _poolToken
        );
    }

    function newBorrowRate(
        address _poolToken
    )
        external
        onlyAllowedContracts
    {
        _newBorrowRate(
            _poolToken
        );
    }

    function _preparePool(
        address _poolToken
    )
        internal
    {
        WISE_SECURITY.curveSecurityCheck(
            _poolToken
        );

        _cleanUp(
            _poolToken
        );

        _updatePseudoTotalAmounts(
            _poolToken
        );
    }

    function _preparationBorrows(
        uint256 _nftId,
        address _poolToken
    )
        internal
    {
        _prepareTokens(
            _poolToken,
            positionBorrowTokenData[_nftId]
        );
    }

    function _preparationCollaterals(
        uint256 _nftId,
        address _poolToken
    )
        internal
    {
        _prepareTokens(
            _poolToken,
            positionLendingTokenData[_nftId]
        );
    }

    function _setIsolationPoolMappings(
        uint256 _nftId,
        address _isolationPool,
        bool _registerState
    )
        internal
    {
        positionLocked[_nftId] = _registerState;

        emit RegisteredForIsolationPool(
            msg.sender,
            _isolationPool,
            _registerState,
            block.timestamp
        );
    }

    function _prepareTokens(
        address _poolToken,
        address[] memory tokens
    )
        private
    {
        address currentAddress;

        for (uint8 i = 0; i < tokens.length; i++) {

            currentAddress = tokens[i];

            if (currentAddress == _poolToken) {
                continue;
            }

            _preparePool(
                currentAddress
            );

            _newBorrowRate(
                _poolToken
            );
        }
    }

    function _updatePseudoTotalAmounts(
        address _poolToken
    )
        internal
    {
        uint256 currentTime = block.timestamp;

        uint256 bareIncrease = borrowPoolData[_poolToken].borrowRate
            * (currentTime - getTimeStamp(_poolToken))
            * getPseudoTotalBorrowAmount(_poolToken)
            + bufferIncrease[_poolToken];

        if (bareIncrease < PRECISION_FACTOR_E18_YEAR) {

            bufferIncrease[_poolToken] = bareIncrease;

            _setTimeStamp(
                _poolToken,
                currentTime
            );

            return;
        }

        bufferIncrease[_poolToken] = 0;

        uint256 amountInterest = bareIncrease
            / PRECISION_FACTOR_E18_YEAR;

        uint256 feeAmount = amountInterest
            * globalPoolData[_poolToken].poolFee
            / PRECISION_FACTOR_E18;

        _increasePseudoTotalBorrowAmount(
            _poolToken,
            amountInterest
        );

        _increasePseudoTotalPool(
            _poolToken,
            amountInterest
        );

        if (feeAmount == 0) {
            return;
        }

        uint256 feeShares = feeAmount
            * getTotalDepositShares(_poolToken)
            / (getPseudoTotalPool(_poolToken) - feeAmount);

        _updatePositionLendingDeposit(
            FEE_MANAGER_NFT,
            _poolToken,
            feeShares
        );

        _increaseTotalDepositShares(
            _poolToken,
            feeShares
        );

        _setTimeStamp(
            _poolToken,
            currentTime
        );
    }

    function _updatePositionLendingDeposit(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        internal
    {
        userLendingData[_nftId][_poolToken].shares += _shares;
    }

    function _updateCollateralize(
        uint256 _nftId,
        address _poolToken,
        address _caller,
        bool _allowAsCollateral
    )
        internal
    {
        userLendingData[_nftId][_poolToken].collaterized = _allowAsCollateral;

        emit CollateralizeDeposit(
            _caller,
            _nftId,
            _poolToken,
            _allowAsCollateral,
            block.timestamp
        );
    }

    function liquidationIncreaseLendingShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        onlyLiquidation
    {
        _updatePositionLendingDeposit(
            _nftId,
            _poolToken,
            _shares
        );
    }

    function _decreaseLendingShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        internal
    {
        userLendingData[_nftId][_poolToken].shares -= _shares;
    }

    function liquidationDecreaseLendingShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        onlyLiquidation
    {
        _decreaseLendingShares(
            _nftId,
            _poolToken,
            _shares
        );
    }

    function liquidationAddPosition(
        uint256 _nftId,
        address _poolToken
    )
        external
        onlyLiquidation
    {
        _addPositionTokenData(
            _nftId,
            _poolToken,
            hashMapPositionLending,
            positionLendingTokenData
        );
    }

    function _addPositionTokenData(
        uint256 _nftId,
        address _poolToken,
        mapping(bytes32 => bool) storage hashMap,
        mapping(uint256 => address[]) storage userTokenData
    )
        internal
    {
        bytes32 hashData = _getHash(
            _nftId,
            _poolToken
        );

        if (hashMap[hashData] == true) {
            return;
        }

        hashMap[hashData] = true;

        userTokenData[_nftId].push(
            _poolToken
        );
    }

    function _getHash(
        uint256 _nftId,
        address _poolToken
    )
        internal
        pure
        returns (bytes32)
    {
        return keccak256(
            abi.encodePacked(
                _nftId,
                _poolToken
            )
        );
    }

    function _removePositionData(
        uint256 _nftId,
        address _poolToken,
        function(uint256) view returns (uint256) _getPositionTokenLength,
        function(uint256, uint256) view returns (address) _getPositionTokenByIndex,
        function(uint256, address) internal _deleteLastPositionData,
        bool isLending
    )
        internal
    {
        uint256 length = _getPositionTokenLength(
            _nftId
        );

        if (length == 1) {
            _deleteLastPositionData(
                _nftId,
                _poolToken
            );

            return;
        }

        uint8 index;
        uint256 endPosition = length - 1;

        while (index < length) {

            if (_getPositionTokenByIndex(_nftId, index) != _poolToken) {
                index += 1;
                continue;
            }

            address poolToken = _getPositionTokenByIndex(
                _nftId,
                endPosition
            );

            isLending
                ? positionLendingTokenData[_nftId][index] = poolToken
                : positionBorrowTokenData[_nftId][index] = poolToken;

            _deleteLastPositionData(
                _nftId,
                _poolToken
            );

            break;
        }
    }

    function _deleteLastPositionLendingData(
        uint256 _nftId,
        address _poolToken
    )
        internal
    {
        positionLendingTokenData[_nftId].pop();
        hashMapPositionLending[
            _getHash(
                _nftId,
                _poolToken
            )
        ] = false;
    }

    function _deleteLastPositionBorrowData(
        uint256 _nftId,
        address _poolToken
    )
        internal
    {
        positionBorrowTokenData[_nftId].pop();
        hashMapPositionBorrow[
            _getHash(
                _nftId,
                _poolToken
            )
        ] = false;
    }

    function getCollateralState(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (bool)
    {
        return userLendingData[_nftId][_poolToken].collaterized;
    }

    function _borrowShareEquivalent(
        address _poolToken,
        uint256 _lendingShares
    )
        internal
        view
        returns (uint256)
    {
        return _lendingShares
            * getPseudoTotalPool(_poolToken)
            * getTotalBorrowShares(_poolToken)
            / getTotalDepositShares(_poolToken)
            / getPseudoTotalBorrowAmount(_poolToken);
    }

    function checkLendingDataEmpty(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (bool)
    {
        return userLendingData[_nftId][_poolToken].shares == 0
            && positionPureCollateralAmount[_nftId][_poolToken] == 0;
    }

    function _calculateNewBorrowRate(
        address _poolToken
    )
        internal
    {
        uint256 pole = borrowRatesData[_poolToken].pole;
        uint256 utilization = globalPoolData[_poolToken].utilization;

        uint256 baseDivider = pole
            * (pole - utilization);

        _setBorrowRate(
            _poolToken,
            borrowRatesData[_poolToken].multiplicativeFactor
                * PRECISION_FACTOR_E18
                * utilization
                / baseDivider
        );
    }

    function _newBorrowRate(
        address _poolToken
    )
        internal
    {
        _updateUtilization(
            _poolToken
        );

        _calculateNewBorrowRate(
            _poolToken
        );

        if (_aboveThreshold(_poolToken) == false) {
            return;
        }

        _scalingAlgorithm(
            _poolToken
        );
    }

    function _aboveThreshold(
        address _poolToken
    )
        private
        view
        returns (bool)
    {
        return block.timestamp - _getTimeStampScaling(_poolToken) >= THREE_HOURS;
    }

    /**
     * @dev function that tries to maximise totalDepositShares of the pool.
     * Reacting to negative and positive feedback by changing the resonance
     * factor of the pool. Method similar to one parameter monte-carlo methods
     */
    function _scalingAlgorithm(
        address _poolToken
    )
        private
    {
        uint256 totalShares = getTotalDepositShares(
            _poolToken
        );

        if (algorithmData[_poolToken].maxValue <= totalShares) {

            _newMaxPoolShares(
                _poolToken,
                totalShares
            );

            _saveUp(
                _poolToken,
                totalShares
            );

            return;
        }

        _resonanceOutcome(_poolToken, totalShares) == true
            ? _resetResonanceFactor(_poolToken, totalShares)
            : _updateResonanceFactor(_poolToken, totalShares);

        _saveUp(
            _poolToken,
            totalShares
        );
    }

    /**
     * @dev Sets the new max value in shares
     * and saves the corresponding resonance factor.
     */
    function _newMaxPoolShares(
        address _poolToken,
        uint256 _shareValue
    )
        private
    {
        _setMaxValue(
            _poolToken,
            _shareValue
        );

        _setBestPole(
            _poolToken,
            borrowRatesData[_poolToken].pole
        );
    }

    function _saveUp(
        address _poolToken,
        uint256 _shareValue
    )
        private
    {
        _setPreviousValue(
            _poolToken,
            _shareValue
        );

        _setTimeStampScaling(
            _poolToken,
            block.timestamp
        );
    }

    /**
     * @dev Returns bool to determine if resonance
     * factor needs to be reset to last best value.
     */
    function _resonanceOutcome(
        address _poolToken,
        uint256 _shareValue
    )
        private
        view
        returns (bool)
    {
        return _shareValue < THRESHOLD_RESET_RESONANCE_FACTOR
            * algorithmData[_poolToken].maxValue
            / PRECISION_FACTOR_E18;
    }

    /**
     * @dev Resets resonance factor to old best value when system
     * evolves into too bad state and sets current totalDepositShares
     * amount to new maxPoolShares to exclude eternal loops and that
     * unorganic peaks do not set maxPoolShares forever.
     */
    function _resetResonanceFactor(
        address _poolToken,
        uint256 _shareValue
    )
        private
    {
        _setPole(
            _poolToken,
            algorithmData[_poolToken].bestPole
        );

        _setMaxValue(
            _poolToken,
            _shareValue
        );

        _revertDirectionSteppingState(
            _poolToken
        );
    }

    /**
     * @dev Reverts the flag for stepping direction from scaling algorithm.
     */
    function _revertDirectionSteppingState(
        address _poolToken
    )
        private
    {
        _setIncreasePole(
            _poolToken,
            !algorithmData[_poolToken].increasePole
        );
    }

    /**
     * @dev Function combining all possible stepping scenarios.
     * Depending how share values has changed compared to last time.
     */
    function _updateResonanceFactor(
        address _poolToken,
        uint256 _shareValues
    )
        private
    {
        _shareValues < THRESHOLD_SWITCH_DIRECTION * algorithmData[_poolToken].previousValue / PRECISION_FACTOR_E18
            ? _reversedChangingResonanceFactor(_poolToken)
            : _changingResonanceFactor(_poolToken);
    }

    /**
     * @dev Does a revert stepping and swaps stepping state in opposite flag.
     */
    function _reversedChangingResonanceFactor(
        address _poolToken
    )
        private
    {
        algorithmData[_poolToken].increasePole
            ? _decreaseResonanceFactor(_poolToken)
            : _increaseResonanceFactor(_poolToken);

        _revertDirectionSteppingState(
            _poolToken
        );
    }

    /**
     * @dev Increasing or decresing resonance factor depending on flag value.
     */
    function _changingResonanceFactor(
        address _poolToken
    )
        private
    {
        algorithmData[_poolToken].increasePole
            ? _increaseResonanceFactor(_poolToken)
            : _decreaseResonanceFactor(_poolToken);
    }

    /**
     * @dev stepping function increasing the
     * resonance factor depending on the time
     * past in the last time interval.
     * Checks if current resonance factor is bigger than max value.
     * If this is the case sets current value to maximal value
     */
    function _increaseResonanceFactor(
        address _poolToken
    )
        private
    {
        BorrowRatesEntry memory borrowData = borrowRatesData[
            _poolToken
        ];

        uint256 delta = borrowData.deltaPole
            * (block.timestamp - _getTimeStampScaling(_poolToken));

        uint256 sum = delta
            + borrowData.pole;

        uint256 setValue = sum > borrowData.maxPole
            ? borrowData.maxPole
            : sum;

        _setPole(
            _poolToken,
            setValue
        );
    }

    /**
     * @dev Stepping function decresing the resonance factor
     * depending on the time past in the last time interval.
     * Checks if current resonance factor undergoes the min value,
     * if this is the case sets current value to minimal value.
     */
    function _decreaseResonanceFactor(
        address _poolToken
    )
        private
    {
        uint256 minValue = borrowRatesData[_poolToken].minPole;

        uint256 delta = borrowRatesData[_poolToken].deltaPole
            * (block.timestamp - _getTimeStampScaling(_poolToken));

        uint256 sub = borrowRatesData[_poolToken].pole > delta
            ? borrowRatesData[_poolToken].pole - delta
            : 0;

        uint256 setValue = sub < minValue
            ? minValue
            : sub;

        _setPole(
            _poolToken,
            setValue
        );
    }

    function _removeEmptyLendingData(
        uint256 _nftId,
        address _poolToken
    )
        internal
    {
        if (_nftId == 0) {
            return;
        }

        if (checkLendingDataEmpty(_nftId, _poolToken) == false) {
            return;
        }

        _removePositionData({
            _nftId: _nftId,
            _poolToken: _poolToken,
            _getPositionTokenLength: getPositionLendingTokenLength,
            _getPositionTokenByIndex: getPositionLendingTokenByIndex,
            _deleteLastPositionData: _deleteLastPositionLendingData,
            isLending: true
        });
    }

    function _updatePoolStorage(
        address _poolToken,
        uint256 _amount,
        uint256 _shares,
        function(address, uint256) functionAmountA,
        function(address, uint256) functionAmountB,
        function(address, uint256) functionSharesA
    )
        internal
    {
        functionAmountA(
            _poolToken,
            _amount
        );

        functionAmountB(
            _poolToken,
            _amount
        );

        functionSharesA(
            _poolToken,
            _shares
        );
    }
}

// File: contracts/TransferHub/TransferHelper.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

import "../InterfaceHub/IERC20.sol";

contract TransferHelper {

    /**
     * @dev
     * Allows to execute transfer for a token
     */
    function _safeTransfer(
        address _token,
        address _to,
        uint256 _value
    )
        internal
    {
        IERC20 token = IERC20(
            _token
        );

        _callOptionalReturnBool(
            _token,
            abi.encodeWithSelector(
                token.transfer.selector,
                _to,
                _value
            )
        );
    }

    /**
     * @dev
     * Allows to execute transferFrom for a token
     */
    function _safeTransferFrom(
        address _token,
        address _from,
        address _to,
        uint256 _value
    )
        internal
    {
        IERC20 token = IERC20(
            _token
        );

        _callOptionalReturnBool(
            _token,
            abi.encodeWithSelector(
                token.transferFrom.selector,
                _from,
                _to,
                _value
            )
        );
    }

    /**
     * @dev
     * Helper function to do the token call
     */
    function _callOptionalReturn(
        address _token,
        bytes memory _data
    )
        private
    {
        (
            bool success,
            bytes memory returndata
        ) = _token.call(_data);

        require(
            success,
            "TransferHelper: CALL_FAILED"
        );

        if (returndata.length > 0) {
            require(
                abi.decode(
                    returndata,
                    (bool)
                ),
                "TransferHelper: OPERATION_FAILED"
            );
        }
    }

    function _callOptionalReturnBool(
        address token,
        bytes memory data
    )
        private
        returns (bool)
    {
        (
            bool success,
            bytes memory returndata
        ) = token.call(
            data
        );

        bool results = returndata.length == 0 || abi.decode(
            returndata,
            (bool)
        );

        return success
            && results
            && token.code.length > 0;
    }
}
// File: contracts/WiseLowLevelHelper.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

import "./WiseLendingDeclaration.sol";

abstract contract WiseLowLevelHelper is WiseLendingDeclaration {

    modifier onlyLiquidation() {
        _onlyLiquidation();
        _;
    }

    function _onlyLiquidation()
        private
        view
    {
        if (msg.sender == address(WISE_LIQUIDATION)) {
            return;
        }

        revert InvalidCaller();
    }

    modifier onlyFeeManager() {
        _onlyFeeManager();
        _;
    }

    function _onlyFeeManager()
        private
        view
    {
        if (msg.sender == address(FEE_MANAGER)) {
            return;
        }

        revert InvalidCaller();
    }

    modifier onlyAllowedContracts() {
        _onlyAllowedContracts();
        _;
    }

    function _onlyAllowedContracts()
        private
        view
    {
        if (msg.sender == address(FEE_MANAGER)) {
            return;
        }

        if (msg.sender == address(WISE_LIQUIDATION)) {
            return;
        }

        if (veryfiedIsolationPool[msg.sender] == true) {
            return;
        }

        if (msg.sender == AAVE_HUB) {
            return;
        }

        revert InvalidCaller();
    }

    // --- Basic Public Views Functions ----

    function getTotalPool(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return globalPoolData[_poolToken].totalPool;
    }

    function getPseudoTotalPool(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return lendingPoolData[_poolToken].pseudoTotalPool;
    }

    function getTotalBareToken(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return globalPoolData[_poolToken].totalBareToken;
    }

    function getPseudoTotalBorrowAmount(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return borrowPoolData[_poolToken].pseudoTotalBorrowAmount;
    }

    function getTotalDepositShares(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return lendingPoolData[_poolToken].totalDepositShares;
    }

    function getTotalBorrowShares(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return borrowPoolData[_poolToken].totalBorrowShares;
    }

    function getPositionLendingShares(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return userLendingData[_nftId][_poolToken].shares;
    }

    function getPositionBorrowShares(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return userBorrowShares[_nftId][_poolToken];
    }

    function getPureCollateralAmount(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return positionPureCollateralAmount[_nftId][_poolToken];
    }

    // --- Basic Internal Get Functions ----

    function getTimeStamp(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return timestampsPoolData[_poolToken].timeStamp;
    }

    function _getTimeStampScaling(
        address _poolToken
    )
        internal
        view
        returns (uint256)
    {
        return timestampsPoolData[_poolToken].timeStampScaling;
    }

    function getPositionLendingTokenByIndex(
        uint256 _nftId,
        uint256 _index
    )
        public
        view
        returns (address)
    {
        return positionLendingTokenData[_nftId][_index];
    }

    function getPositionLendingTokenLength(
        uint256 _nftId
    )
        public
        view
        returns (uint256)
    {
        return positionLendingTokenData[_nftId].length;
    }

    function getPositionBorrowTokenByIndex(
        uint256 _nftId,
        uint256 _index
    )
        public
        view
        returns (address)
    {
        return positionBorrowTokenData[_nftId][_index];
    }

    function getPositionBorrowTokenLength(
        uint256 _nftId
    )
        public
        view
        returns (uint256)
    {
        return positionBorrowTokenData[_nftId].length;
    }

    // --- Basic Internal Set Functions ----

    function _setLastUpdated(
        address _poolAddress
    )
        internal
    {
        lastUpdated[_poolAddress] = block.timestamp;
    }

    function _setMaxValue(
        address _poolToken,
        uint256 _value
    )
        internal
    {
        algorithmData[_poolToken].maxValue = _value;
    }

    function _setPreviousValue(
        address _poolToken,
        uint256 _value
    )
        internal
    {
        algorithmData[_poolToken].previousValue = _value;
    }

    function _setBestPole(
        address _poolToken,
        uint256 _value
    )
        internal
    {
        algorithmData[_poolToken].bestPole = _value;
    }

    function _setIncreasePole(
        address _poolToken,
        bool _state
    )
        internal
    {
        algorithmData[_poolToken].increasePole = _state;
    }

    function _setPole(
        address _poolToken,
        uint256 _value
    )
        internal
    {
        borrowRatesData[_poolToken].pole = _value;
    }

    function _increaseTotalPool(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        globalPoolData[_poolToken].totalPool += _amount;
    }

    function _decreaseTotalPool(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        globalPoolData[_poolToken].totalPool -= _amount;
    }

    function _increaseTotalDepositShares(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        lendingPoolData[_poolToken].totalDepositShares += _amount;
    }

    function _decreaseTotalDepositShares(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        lendingPoolData[_poolToken].totalDepositShares -= _amount;
    }

    function _increasePseudoTotalBorrowAmount(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        borrowPoolData[_poolToken].pseudoTotalBorrowAmount += _amount;
    }

    function _decreasePseudoTotalBorrowAmount(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        borrowPoolData[_poolToken].pseudoTotalBorrowAmount -= _amount;
    }

    function _increaseTotalBorrowShares(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        borrowPoolData[_poolToken].totalBorrowShares += _amount;
    }

    function _decreaseTotalBorrowShares(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        borrowPoolData[_poolToken].totalBorrowShares -= _amount;
    }

    function _increasePseudoTotalPool(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        lendingPoolData[_poolToken].pseudoTotalPool += _amount;
    }

    function _decreasePseudoTotalPool(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        lendingPoolData[_poolToken].pseudoTotalPool -= _amount;
    }

    function _setBorrowRate(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        borrowPoolData[_poolToken].borrowRate = _amount;
    }

    function _setTimeStamp(
        address _poolToken,
        uint256 _time
    )
        internal
    {
        timestampsPoolData[_poolToken].timeStamp = _time;
    }

    function _setTimeStampScaling(
        address _poolToken,
        uint256 _time
    )
        internal
    {
        timestampsPoolData[_poolToken].timeStampScaling = _time;
    }

    function _increaseTotalBareToken(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        globalPoolData[_poolToken].totalBareToken += _amount;
    }

    function _decreaseTotalBareToken(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        globalPoolData[_poolToken].totalBareToken -= _amount;
    }

    function _reduceAllowance(
        uint256 _nftId,
        address _poolToken,
        address _spender,
        uint256 _amount
    )
        internal
    {
        if (POSITION_NFT.getApproved(_nftId) == _spender) {
            return;
        }

        address owner = POSITION_NFT.ownerOf(
            _nftId
        );

        if (allowance[owner][_poolToken][_spender] != UINT256_MAX) {
            allowance[owner][_poolToken][_spender] -= _amount;
        }
    }

    function _decreasePositionMappingValue(
        mapping(uint256 => mapping(address => uint256)) storage userMapping,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        userMapping[_nftId][_poolToken] -= _amount;
    }

    function _increaseMappingValue(
        mapping(uint256 => mapping(address => uint256)) storage userMapping,
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        userMapping[_nftId][_poolToken] += _amount;
    }

    function liquidationDecreaseCollateral(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        onlyLiquidation
    {
        _decreasePositionMappingValue(
            positionPureCollateralAmount,
            _nftId,
            _poolToken,
            _amount
        );
    }

    function liquidationDecreaseTotalBareToken(
        address _poolToken,
        uint256 _amount
    )
        external
        onlyLiquidation
    {
        _decreaseTotalBareToken(
            _poolToken,
            _amount
        );
    }

    function _increaseTotalAndPseudoTotalPool(
        address _poolToken,
        uint256 _amount
    )
        internal
    {
        _increasePseudoTotalPool(
            _poolToken,
            _amount
        );

        _increaseTotalPool(
            _poolToken,
            _amount
        );
    }

    function setPoolFee(
        address _poolToken,
        uint256 _newFee
    )
        external
        onlyFeeManager
    {
        globalPoolData[_poolToken].poolFee = _newFee;
    }
}

// File: contracts/WiseLendingDeclaration.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

import "./InterfaceHub/IWETH.sol";
import "./InterfaceHub/IFeeManager.sol";
import "./InterfaceHub/IPositionNFTs.sol";
import "./InterfaceHub/IWiseSecurity.sol";
import "./InterfaceHub/IWiseLiquidation.sol";
import "./InterfaceHub/IWiseOracleHub.sol";

import "./OwnableMaster.sol";

error AlreadyCreated();
error InvalidCaller();
error ParametersLocked();
error ForbiddenValue();

contract WiseLendingDeclaration is OwnableMaster {

    event FundsDeposited(
        address indexed sender,
        uint256 indexed nftId,
        address indexed token,
        uint256 amount,
        uint256 shares,
        uint256 timestamp
    );

    event FundsSolelyDeposited(
        address indexed sender,
        uint256 indexed nftId,
        address indexed token,
        uint256 amount,
        uint256 timestamp
    );

    event FundsWithdrawn(
        address indexed sender,
        uint256 indexed nftId,
        address indexed token,
        uint256 amount,
        uint256 shares,
        uint256 timestamp
    );

    event FundsWithdrawnOnBehalf(
        address indexed sender,
        uint256 indexed nftId,
        address indexed token,
        uint256 amount,
        uint256 shares,
        uint256 timestamp
    );

    event FundsSolelyWithdrawn(
        address indexed sender,
        uint256 indexed nftId,
        address indexed token,
        uint256 amount,
        uint256 timestamp
    );

    event FundsSolelyWithdrawnOnBehalf(
        address indexed sender,
        uint256 indexed nftId,
        address indexed token,
        uint256 amount,
        uint256 timestamp
    );

    event FundsBorrowed(
        address indexed borrower,
        uint256 indexed nftId,
        address indexed token,
        uint256 amount,
        uint256 shares,
        uint256 timestamp
    );

    event FundsBorrowedOnBehalf(
        address indexed sender,
        uint256 indexed nftId,
        address indexed token,
        uint256 amount,
        uint256 shares,
        uint256 timestamp
    );

    event FundsReturned(
        address indexed sender,
        address indexed token,
        uint256 indexed nftId,
        uint256 totalPayment,
        uint256 totalPaymentShares,
        uint256 timestamp
    );

    event FundsReturnedWithLendingShares(
        address indexed sender,
        address indexed token,
        uint256 indexed nftId,
        uint256 totalPayment,
        uint256 totalPaymentShares,
        uint256 timestamp
    );

    event CollateralizeDeposit(
        address indexed user,
        uint256 indexed nftId,
        address indexed token,
        bool allowAsCollateral,
        uint256 timestamp
    );

    event DecollateralizeDeposit(
        address user,
        address token,
        uint256 timestamp
    );

    event Approve(
        address indexed sender,
        address indexed token,
        address indexed user,
        uint256 amount,
        uint256 timestamp
    );

    event PoolSynced(
        address pool,
        uint256 timestamp
    );

    event RegisteredForIsolationPool(
        address indexed user,
        address indexed isolationContractAddress,
        bool indexed registration,
        uint256 timestamp
    );

    constructor(
        address _master,
        address _wiseOracleHub,
        address _nftContract,
        address _wethContract
    )
        OwnableMaster(
            _master
        )
    {
        WETH_ADDRESS = _wethContract;

        WETH = IWETH(
            _wethContract
        );

        WISE_ORACLE = IWiseOracleHub(
            _wiseOracleHub
        );

        POSITION_NFT = IPositionNFTs(
            _nftContract
        );
    }

    function setSecurity(
        address _wiseSecurity
    )
        external
        onlyMaster
    {
        WISE_SECURITY = IWiseSecurity(
            _wiseSecurity
        );

        FEE_MANAGER = IFeeManager(
            WISE_SECURITY.FEE_MANAGER()
        );

        WISE_LIQUIDATION = IWiseLiquidation(
            WISE_SECURITY.WISE_LIQUIDATION()
        );

        AAVE_HUB = WISE_SECURITY.AAVE_HUB();
    }

    function _wrapETH(
        uint256 _value
    )
        internal
    {
        WETH.deposit{
            value: _value
        }();
    }

    function _unwrapETH(
        uint256 _value
    )
        internal
    {
        WETH.withdraw(
            _value
        );
    }

    IWETH immutable WETH;
    uint256 constant FEE_MANAGER_NFT = 0;

    address public AAVE_HUB;
    IFeeManager public FEE_MANAGER;
    IWiseSecurity public WISE_SECURITY;
    IWiseLiquidation public WISE_LIQUIDATION;

    address public immutable WETH_ADDRESS;
    IPositionNFTs public immutable POSITION_NFT;
    IWiseOracleHub public immutable WISE_ORACLE;

    struct LendingEntry {
        uint256 shares;
        bool collaterized;
    }

    struct BorrowRatesEntry {
        uint256 pole;
        uint256 deltaPole;
        uint256 minPole;
        uint256 maxPole;
        uint256 multiplicativeFactor;
    }

    struct AlgorithmEntry {
        uint256 bestPole;
        uint256 maxValue;
        uint256 previousValue;
        bool increasePole;
    }

    struct GlobalPoolEntry {
        uint256 totalPool;
        uint256 utilization;
        uint256 totalBareToken;
        uint256 poolFee;
    }

    struct LendingPoolEntry {
        uint256 pseudoTotalPool;
        uint256 totalDepositShares;
        uint256 collateralFactor;
    }

    struct BorrowPoolEntry {
        bool allowBorrow;
        uint256 pseudoTotalBorrowAmount;
        uint256 totalBorrowShares;
        uint256 borrowRate;
    }

    struct TimestampsPoolEntry {
        uint256 timeStamp;
        uint256 timeStampScaling;
    }

    // User mappings ------------------------------------------
    mapping(address => uint256) bufferIncrease;
    mapping(address => uint256) public lastUpdated;
    mapping(address => uint256) public maxDepositValueToken;

    mapping(uint256 => address[]) public positionBorrowTokenData;
    mapping(uint256 => address[]) public positionLendingTokenData;

    mapping(uint256 => mapping(address => uint256)) public userBorrowShares;
    mapping(uint256 => mapping(address => LendingEntry)) public userLendingData;
    mapping(uint256 => mapping(address => uint256)) public positionPureCollateralAmount;

    // Owner -> PoolToken -> Spender -> Allowance Value
    mapping(address => mapping(address => mapping(address => uint256))) public allowance;

    // Struct mappings -------------------------------------
    mapping(address => BorrowRatesEntry) public borrowRatesData;
    mapping(address => AlgorithmEntry) public algorithmData;
    mapping(address => GlobalPoolEntry) public globalPoolData;
    mapping(address => LendingPoolEntry) public lendingPoolData;
    mapping(address => BorrowPoolEntry) public borrowPoolData;
    mapping(address => TimestampsPoolEntry) public timestampsPoolData;

    // Bool mappings -------------------------------------
    mapping(uint256 => bool) public positionLocked;
    mapping(address => bool) public parametersLocked;
    mapping(address => bool) public veryfiedIsolationPool;

    // Hash mappings -------------------------------------
    mapping(bytes32 => bool) hashMapPositionBorrow;
    mapping(bytes32 => bool) hashMapPositionLending;

    // PRECISION FACTORS ------------------------------------
    uint256 constant PRECISION_FACTOR_E16 = 1E16;
    uint256 constant PRECISION_FACTOR_E18 = 1E18;
    uint256 constant PRECISION_FACTOR_E36 = PRECISION_FACTOR_E18 * PRECISION_FACTOR_E18;

    // TIME CONSTANTS --------------------------------------
    uint256 constant ONE_YEAR = 52 weeks;
    uint256 constant THREE_HOURS = 3 hours;
    uint256 constant PRECISION_FACTOR_E18_YEAR = PRECISION_FACTOR_E18 * ONE_YEAR;

    // Two months in seconds:
    // Norming change in R-factor that it steps from min to max value
    // within two month (if nothing changes)
    uint256 constant NORMALISATION_FACTOR = 4838400;

    // SCALING ALGORITHM CONSTANTS -------------------------
    uint256 constant THRESHOLD_RESET_RESONANCE_FACTOR = 75 * PRECISION_FACTOR_E16;
    uint256 constant THRESHOLD_SWITCH_DIRECTION = 90 * PRECISION_FACTOR_E16;

    /// OTHERS
    uint256 constant UINT256_MAX = type(uint256).max;
}

// File: contracts/InterfaceHub/IERC20.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

interface IERC20 {

    function totalSupply()
        external
        view
        returns (uint256);

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

    function transferFrom(
        address _sender,
        address _recipient,
        uint256 _amount
    )
        external
        returns (bool);

    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 decimals()
        external
        view
        returns (uint8);

    event Transfer(
        address indexed from,
        address indexed to,
        uint256 value
    );

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );

    event  Deposit(
        address indexed dst,
        uint wad
    );

    event  Withdrawal(
        address indexed src,
        uint wad
    );
}

// File: contracts/OwnableMaster.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

error NotMaster();
error NotProposed();

contract OwnableMaster {

    address public master;
    address public proposedMaster;

    address constant ZERO_ADDRESS = address(0x0);

    modifier onlyProposed() {
        _onlyProposed();
        _;
    }

    function _onlyMaster()
        private
        view
    {
        if (msg.sender == master) {
            return;
        }

        revert NotMaster();
    }

    modifier onlyMaster() {
        _onlyMaster();
        _;
    }

    function _onlyProposed()
        private
        view
    {
        if (msg.sender == proposedMaster) {
            return;
        }

        revert NotProposed();
    }

    event ProposedOwner(
        address proposed,
        uint256 timestamp
    );

    event ClaimedOwnership(
        address master,
        uint256 timestamp
    );

    constructor(
        address _master
    ) {
        master = _master;
    }

    /**
     * @dev Allows to propose next master.
     * Must be claimed by proposer.
     */
    function proposeOwner(
        address _proposedOwner
    )
        external
        onlyMaster
    {
        proposedMaster = _proposedOwner;

        emit ProposedOwner(
            _proposedOwner,
            block.timestamp
        );
    }

    /**
     * @dev Allows to claim master role.
     * Must be called by proposer.
     */
    function claimOwnership()
        external
        onlyProposed
    {
        master = proposedMaster;

        emit ClaimedOwnership(
            proposedMaster,
            block.timestamp
        );
    }

    /**
     * @dev Removes master role.
     * No ability to be in control.
     */
    function renounceOwnership()
        external
        onlyMaster
    {
        master = ZERO_ADDRESS;
        proposedMaster = ZERO_ADDRESS;
    }
}
// File: contracts/InterfaceHub/IWiseOracleHub.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

interface IWiseOracleHub {

    function latestResolver(
        address _tokenAddress
    )
        external
        view
        returns (uint256);

    function getTokensFromUSD(
        address _tokenAddress,
        uint256 _usdValue
    )
        external
        view
        returns (uint256);

    function getTokensInUSD(
        address _tokenAddress,
        uint256 _amount
    )
        external
        view
        returns (uint256);

    function chainLinkIsDead(
        address _tokenAddress
    )
        external
        view
        returns (bool);

    function targetDecimalsUSD()
        external
        pure
        returns (uint256);

    function previousValue(
        address _tokenAddress
    )
        external
        view
        returns (uint256);

    function setPreviousValue(
        address _tokenAddress
    )
        external;
}
// File: contracts/InterfaceHub/IWiseLiquidation.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

interface IWiseLiquidation {

    function coreLiquidationIsolationPools(
        uint256 _nftId,
        uint256 _nftIdLiquidator,
        address _caller,
        address _receiver,
        address _tokenToPayback,
        address _tokenToRecieve,
        uint256 _paybackAmount,
        uint256 _shareAmountToPay
    )
        external
        returns (uint256 reveiveAmount);

    function liquidatePartiallyFromTokens(
        uint256 _nftId,
        uint256 _nftIdLiquidator,
        address _tokenToPayback,
        address _tokenToRecieve,
        uint256 _shareAmountToPay
    )
        external;
}

// File: contracts/InterfaceHub/IWiseSecurity.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

struct CurveSwapStruct {
    uint256 curvePoolTokenIndexFrom;
    uint256 curvePoolTokenIndexTo;
    uint256 curveMetaPoolTokenIndexFrom;
    uint256 curveMetaPoolTokenIndexTo;
    uint256 curvePoolSwapAmount;
    uint256 curveMetaPoolSwapAmount;
}

interface IWiseSecurity {

    function overallUSDBorrowHeartbeat(
        uint256 _nftId
    )
        external
        view
        returns (uint256 buffer);

    function checkBadDebt(
        uint256 _nftId
    )
        external;

    function getFullCollateralUSD(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (uint256);

    function checksLiquidation(
        uint256 _nftIdLiquidate,
        address _caller,
        address _tokenToPayback,
        uint256 _shareAmountToPay
    )
        external
        view;

    function onlyIsolationPool(
        address _poolAddress
    )
        external
        view;

    function getPositionBorrowAmount(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (uint256);

    function getPositionLendingAmount(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
        returns (uint256);

    function getLiveDebtratioNormalPool(
        uint256 _nftId
    )
        external
        view
        returns (uint256);

    function overallUSDCollateralsBare(
        uint256 _nftId
    )
        external
        view
        returns (uint256 amount);

    function FEE_MANAGER()
        external
        returns (address);

    function AAVE_HUB()
        external
        returns (address);

    function WISE_LIQUIDATION()
        external
        returns (address);

    function curveSecurityCheck(
        address _poolAddress
    )
        external;

    function prepareCurvePools(
        address _poolToken,
        address _curvePool,
        address _curveMetaPool,
        CurveSwapStruct memory _curveSwapStruct
    )
        external;

    function setUnderlyingPoolTokensFromPoolToken(
        address _poolToken,
        address[] memory _underlyingTokens
    )
        external;

    function checksDeposit(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checksWithdraw(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checksBorrow(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checksSolelyWithdraw(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checkOwnerPosition(
        uint256 _nftId,
        address _caller
    )
        external
        view;

    function checksCollateralizeDeposit(
        uint256 _nftIdCaller,
        address _caller,
        address _poolAddress
    )
        external
        view;

    function checksDecollateralizeDeposit(
        uint256 _nftIdCaller,
        address _poolToken
    )
        external
        view;

    function checkBorrowLimit(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checkPositionLocked(
        uint256 _nftId,
        address _caller
    )
        external
        view;

    function checkPaybackLendingShares(
        uint256 _nftIdReceiver,
        uint256 _nftIdCaller,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view;

    function checksRegistrationIsolationPool(
        uint256 _nftId,
        address _caller,
        address _isolationPool
    )
        external
        view;

    function checksRegister(
        uint256 _nftId,
        address _caller
    )
        external
        view;

    function getLendingRate(
        address _poolToken
    )
        external
        view
        returns (uint256);
}
// File: contracts/InterfaceHub/IPositionNFTs.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

interface IPositionNFTs {

    function ownerOf(
        uint256 _nftId
    )
        external
        view
        returns (address);

    function getOwner(
        uint256 _nftId
    )
        external
        view
        returns (address);


    function totalSupply()
        external
        view
        returns (uint256);

    function mintPosition()
        external;

    function tokenOfOwnerByIndex(
        address _owner,
        uint256 _index
    )
        external
        view
        returns (uint256);

    function mintPositionForUser(
        address _user
    )
        external
        returns (uint256);

    function getApproved(
        uint256 _nftId
    )
        external
        returns (address);
}
// File: contracts/InterfaceHub/IFeeManager.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

interface IFeeManager {

    function setBadDebtUserLiquidation(
        uint256 _nftId,
        uint256 _amount
    )
        external;

    function increaseTotalBadDebtLiquidation(
        uint256 _amount
    )
        external;

    function FEE_MASTER_NFT_ID()
        external
        returns (uint256);

    function addPoolTokenAddress(
        address _poolToken
    )
        external;

    function getPoolTokenAdressesByIndex(
        uint256 _index
    )
        external
        view
        returns (address);

    function getPoolTokenAddressesLength()
        external
        view
        returns (uint256);
}
// File: contracts/InterfaceHub/IWETH.sol



// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.21;

import "./IERC20.sol";

interface IWETH is IERC20 {

    function deposit()
        external
        payable;

    function withdraw(
        uint256
    )
        external;
}