// File: contracts/WiseSecurity/WiseSecurity.sol



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

pragma solidity =0.8.21;

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

import "./WiseSecurityHelper.sol";
import "../TransferHub/ApprovalHelper.sol";

/**
 * @dev WiseSecurity is a core contract for wiseLending including most of
 * the performed security checks for withdraws, borrows, paybacks and liquidations.
 * It also has several read only functions providing UI data for a better user
 * experiencne.
 *
 */

error NotWiseLendingSecurity();

contract WiseSecurity is WiseSecurityHelper, ApprovalHelper {

    modifier onlyWiseLending() {
        _onlyWiseLending();
        _;
    }

    function _onlyWiseLending()
        private
        view
    {
        if (msg.sender == address(WISE_LENDING)) {
            return;
        }

        revert NotWiseLendingSecurity();
    }

    constructor(
        address _master,
        address _wiseLendingAddress,
        address _aaveHubAddress,
        uint256 _borrowPercentageCap
    )
        WiseSecurityDeclarations(
            _master,
            _wiseLendingAddress,
            _aaveHubAddress,
            _borrowPercentageCap
        )
    {}

    /**
     * @dev View functions returning current
     * debt ratio of a postion in normal mode.
     * 1% <=> 1E16
     */
    function getLiveDebtratioNormalPool(
        uint256 _nftId
    )
        external
        view
        returns (uint256)
    {
        uint256 overallCollateral = overallUSDCollateralsWeighted(
            _nftId
        );

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

        return overallUSDBorrow(_nftId)
            * PRECISION_FACTOR_E18
            / overallCollateral;
    }

    /**
     * @dev Set functions to define underlying tokens
     * for a pool token. (E.g underlying token of a
     * LP token like Curve, Uniswap).
     */
    function setLiquidationSettings(
        uint256 _baseReward,
        uint256 _baseRewardFarm,
        uint256 _newMaxFeeUSD,
        uint256 _newMaxFeeFarmUSD
    )
        external
        onlyMaster
    {
        if (_baseReward > 0) {
            baseRewardLiquidation = _baseReward;
        }

        if (_baseRewardFarm > 0) {
            baseRewardLiquidationFarm = _baseRewardFarm;
        }

        if (_newMaxFeeUSD > 0) {
            maxFeeUSD = _newMaxFeeUSD;
        }

        if (_newMaxFeeFarmUSD > 0) {
            maxFeeFarmUSD = _newMaxFeeFarmUSD;
        }
    }

    /**
     * @dev Checks for liquidation logic.
     */
    function checksLiquidation(
        uint256 _nftIdLiquidate,
        address _tokenToPayback,
        uint256 _shareAmountToPay
    )
        external
        view
    {
        (
            uint256 weightedCollateralUSD,
            uint256 unweightedCollateralUSD

        ) = overallUSDCollateralsBoth(
            _nftIdLiquidate
        );

        uint256 borrowUSDTotal = overallUSDBorrowHeartbeat(
            _nftIdLiquidate
        );

        canLiquidate(
            borrowUSDTotal,
            weightedCollateralUSD
        );

        checkMaxShares(
            _nftIdLiquidate,
            _tokenToPayback,
            borrowUSDTotal,
            unweightedCollateralUSD,
            _shareAmountToPay
        );
    }

    /**
     * @dev Set function for preparing curve pools.
     */
    function prepareCurvePools(
        address _poolToken,
        CurveSwapStructData memory _curveSwapStructData,
        CurveSwapStructToken memory _curveSwapStructToken
    )
        external
        onlyWiseLending
    {
        curveSwapInfoData[_poolToken] = _curveSwapStructData;
        curveSwapInfoToken[_poolToken] = _curveSwapStructToken;

        address curvePool = curveSwapInfoData[_poolToken].curvePool;
        uint256 tokenIndexForApprove = _curveSwapStructToken.curvePoolTokenIndexFrom;

        _safeApprove(
            ICurve(curvePool).coins(tokenIndexForApprove),
            curvePool,
            0
        );

        _safeApprove(
            ICurve(curvePool).coins(tokenIndexForApprove),
            curvePool,
            UINT256_MAX
        );

        address curveMetaPool = curveSwapInfoData[_poolToken].curveMetaPool;

        if (curveMetaPool == ZERO_ADDRESS) {
            return;
        }

        tokenIndexForApprove = _curveSwapStructToken.curveMetaPoolTokenIndexFrom;

        _safeApprove(
            ICurve(curvePool).coins(tokenIndexForApprove),
            curveMetaPool,
            0
        );

        _safeApprove(
            ICurve(curveMetaPool).coins(tokenIndexForApprove),
            curveMetaPool,
            UINT256_MAX
        );
    }

    /**
     * @dev Reentrency guard for curve pools. Forces
     * a swap to update internal curve values.
     */
    function curveSecurityCheck(
        address _poolToken
    )
        external
        onlyWiseLending
    {
        address curvePool = curveSwapInfoData[_poolToken].curvePool;

        if (curvePool == ZERO_ADDRESS) {
            return;
        }

        (
            bool success
            ,
        ) = curvePool.call{value: 0} (
            curveSwapInfoData[_poolToken].swapBytesPool
        );

        _checkSuccess(
            success
        );

        address curveMeta = curveSwapInfoData[_poolToken].curveMetaPool;

        if (curveMeta == ZERO_ADDRESS) {
            return;
        }

        (
            success
            ,
        ) = curveMeta.call{value: 0} (
            curveSwapInfoData[_poolToken].swapBytesMeta
        );

        _checkSuccess(
            success
        );
    }

    /**
     * @dev Checks for withdraw logic.
     */
    function checksWithdraw(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view
    {
        if (_checkConditions(_poolToken) == true) {

            if (_overallUSDBorrowBare(_nftId) > 0) {
                revert OpenBorrowPosition();
            }

            return;
        }

        if (_caller == AAVE_HUB) {
            return;
        }

        if (WISE_LENDING.veryfiedIsolationPool(_caller) == true) {
            return;
        }

        _checkPositionLocked(
            _nftId
        );

        if (_isDecollateralized(_nftId, _poolToken) == true) {
            return;
        }

        checkBorrowLimit(
            _nftId,
            _poolToken,
            _amount
        );
    }

    /**
     * @dev Checks for solely withdraw logic.
     */
    function checksSolelyWithdraw(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view
    {
        if (_checkConditions(_poolToken) == true) {

            if (_overallUSDBorrowBare(_nftId) > 0) {
                revert OpenBorrowPosition();
            }

            return;
        }

        if (_caller == AAVE_HUB) {
            return;
        }

        _checkPositionLocked(
            _nftId
        );

        checkBorrowLimit(
            _nftId,
            _poolToken,
            _amount
        );
    }

    /**
     * @dev Checks for borrow logic.
     */
    function checksBorrow(
        uint256 _nftId,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view
    {
        _tokenChecks(
            _poolToken
        );

        checkTokenAllowed(
            _poolToken
        );

        if (WISE_LENDING.veryfiedIsolationPool(_caller) == true) {
            return;
        }

        if (_caller == AAVE_HUB) {
            return;
        }

        _checkPositionLocked(
            _nftId
        );

        _checkBorrowPossible(
            _nftId,
            _poolToken,
            _amount
        );
    }

    /**
     * @dev Checks for payback with lending
     * shares logic.
     */
    function checkPaybackLendingShares(
        uint256 _nftIdReceiver,
        uint256 _nftIdCaller,
        address _caller,
        address _poolToken,
        uint256 _amount
    )
        external
        view
    {
        checkOwnerPosition(
            _nftIdCaller,
            _caller
        );

        _checkPositionLocked(
            _nftIdReceiver
        );

        _checkPositionLocked(
            _nftIdCaller
        );

        if (_isDecollateralized(_nftIdCaller, _poolToken) == true) {
            return;
        }

        checkBorrowLimit(
            _nftIdCaller,
            _poolToken,
            _amount
        );
    }

    /**
     * @dev Checks for collateralize deposit logic.
     */
    function checksCollateralizeDeposit(
        uint256 _nftId,
        address _caller,
        address _poolAddress
    )
        external
        view
    {
        if (checkHeartbeat(_poolAddress) == false) {
            revert ChainlinkDead();
        }

        checkOwnerPosition(
            _nftId,
            _caller
        );
    }

    /**
     * @dev Checks for de-collateralize deposit logic.
     */
    function checksDecollateralizeDeposit(
        uint256 _nftId,
        address _poolToken
    )
        external
        view
    {
        if (_checkConditions(_poolToken) == true) {

            if (_overallUSDBorrowBare(_nftId) > 0) {
                revert OpenBorrowPosition();
            }

            return;
        }

        checkBorrowLimit({
            _nftId: _nftId,
            _poolToken: _poolToken,
            _amount: 0
        });
    }

    /**
     * @dev Checks for bad debt logic. Compares
     * total USD of borrow and collateral.
     */
    function checkBadDebt(
        uint256 _nftId
    )
        external
        onlyWiseLending
    {

        uint256 bareCollateral = overallUSDCollateralsBare(
            _nftId
        );

        uint256 totalBorrow = _overallUSDBorrowBare(
            _nftId
        );

        if (totalBorrow < bareCollateral) {
            return;
        }

        uint256 diff = totalBorrow
            - bareCollateral;

        FEE_MANAGER.increaseTotalBadDebtLiquidation(
            diff
        );

        FEE_MANAGER.setBadDebtUserLiquidation(
            _nftId,
            diff
        );
    }

    /**
     * @dev View function returning weighted
     * supply APY of a postion. 1% <=> 1E16
     */
    function overallLendingAPY(
        uint256 _nftId
    )
        external
        view
        returns (uint256)
    {
        uint256 len = WISE_LENDING.getPositionLendingTokenLength(
            _nftId
        );

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

        uint8 i;
        address token;
        uint256 amount;
        uint256 usdValue;
        uint256 overallUSD;
        uint256 weightedRate;

        for (i = 0; i < len; ++i) {

            token = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            amount = getPositionLendingAmount(
                _nftId,
                token
            );

            usdValue = WISE_ORACLE.getTokensInUSD(
                token,
                amount
            );

            weightedRate += usdValue
                * getLendingRate(token);

            overallUSD += usdValue;
        }

        return weightedRate
            / overallUSD;
    }

    /**
     * @dev View function returning weighted
     * borrow APY of a postion. 1% <=> 1E16
     */
    function overallBorrowAPY(
        uint256 _nftId
    )
        external
        view
        returns (uint256)
    {
        uint256 len = WISE_LENDING.getPositionBorrowTokenLength(
            _nftId
        );

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

        uint8 i;
        address token;
        uint256 amount;
        uint256 usdValue;
        uint256 overallUSD;
        uint256 weightedRate;

        for (i = 0; i < len; ++i) {

            token = WISE_LENDING.getPositionBorrowTokenByIndex(
                _nftId,
                i
            );

            amount = getPositionBorrowAmount(
                _nftId,
                token
            );

            usdValue = WISE_ORACLE.getTokensInUSD(
                token,
                amount
            );

            weightedRate += usdValue
                * getBorrowRate(token);

            overallUSD += usdValue;
        }

        return weightedRate
            / overallUSD;
    }

    /**
     * @dev View function returning the total
     * net APY of a postion. 1% <=> 1E16
     */
    function overallNetAPY(
        uint256 _nftId
    )
        external
        view
        returns (uint256, bool)
    {
        uint8 i;
        address token;
        uint256 usdValue;
        uint256 usdValueDebt;
        uint256 usdValueGain;
        uint256 totalUsdSupply;

        uint256 netAPY;

        uint256 lenBorrow = WISE_LENDING.getPositionBorrowTokenLength(
            _nftId
        );

        uint256 lenDeposit = WISE_LENDING.getPositionLendingTokenLength(
            _nftId
        );

        for (i = 0; i < lenBorrow; ++i) {

            token = WISE_LENDING.getPositionBorrowTokenByIndex(
                _nftId,
                i
            );

            usdValue = getUSDBorrow(
                _nftId,
                token
            );

            usdValueDebt += usdValue
                * getBorrowRate(token);
        }

        for (i = 0; i < lenDeposit; ++i) {

            token = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            usdValue = getUSDCollateral(
                _nftId,
                token
            );

            totalUsdSupply += usdValue;
            usdValueGain += usdValue
                * getLendingRate(token);
        }

        if (usdValueGain >= usdValueDebt) {

            netAPY = (usdValueGain - usdValueDebt)
                / totalUsdSupply;

            return (netAPY, false);
        }

        netAPY = (usdValueDebt - usdValueGain)
                / totalUsdSupply;

        return (netAPY, true);
    }

    /**
     * @dev View function claculating the open
     * amount a postion is allowed to borrow.
     */
    function safeLimitPosition(
        uint256 _nftId
    )
        external
        view
        returns (uint256)
    {
        uint256 len = WISE_LENDING.getPositionLendingTokenLength(
            _nftId
        );

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

        uint256 i;
        address token;
        uint256 buffer;

        for (i = 0; i < len; ++i) {

            token = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            if (checkHeartbeat(token) == false) {
                continue;
            }

            if (wasBlacklisted[token] == true) {
                continue;
            }

            buffer += WISE_LENDING.lendingPoolData(token).collateralFactor
                * getFullCollateralUSD(
                    _nftId,
                    token
                ) / PRECISION_FACTOR_E18;
        }

        return buffer
            * borrowPercentageCap
            / PRECISION_FACTOR_E18;
    }

    /**
     * @dev View function checking if the postion is
     * locked due to blacklisted token or token which
     * chainlink oracles are dead a.k.a having no
     * heartbeat.
     */
    function positionLocked(
        uint256 _nftId
    )
        external
        view
        returns (bool, address)
    {
        uint256 len = WISE_LENDING.getPositionLendingTokenLength(
            _nftId
        );

        uint256 i;
        address token;

        for (i = 0; i < len; ++i) {

            token = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            if (_checkConditions(token) == true) {
                return (
                    true,
                    token
                );
            }
        }

        uint256 lenBorrow = WISE_LENDING.getPositionBorrowTokenLength(
            _nftId
        );

        for (i = 0; i < lenBorrow; ++i) {

            token = WISE_LENDING.getPositionBorrowTokenByIndex(
                _nftId,
                i
            );

            if (_checkConditions(token) == true) {
                return (
                    true,
                    token
                );
            }
        }

        return (
            false,
            ZERO_ADDRESS
        );
    }

    /**
     * @dev View function extrapolating the
     * possible withdraw amount of a postion
     * for a specific _poolToken.
     */
    function maximumWithdrawToken(
        uint256 _nftId,
        address _poolToken,
        uint256 _interval,
        uint256 _solelyWithdrawAmount
    )
        external
        view
        returns (uint256)
    {
        uint256 withdrawAmount;

        uint256 expectedMaxAmount = getExpectedLendingAmount(
            _nftId,
            _poolToken,
            _interval
        );

        uint256 maxAmountPool = WISE_LENDING.getTotalPool(
            _poolToken
        );

        withdrawAmount = expectedMaxAmount;

        if (expectedMaxAmount > maxAmountPool) {
            withdrawAmount = maxAmountPool;
        }

        if (_isDecollateralized(_nftId, _poolToken) == true) {
            return withdrawAmount;
        }

        uint256 possibelWithdraw = _getPossibleWithdrawAmount(
            _nftId,
            _poolToken,
            _interval
        );

        withdrawAmount = possibelWithdraw;

        if (possibelWithdraw > expectedMaxAmount) {
            withdrawAmount = expectedMaxAmount;
        }

        if (_solelyWithdrawAmount >= withdrawAmount) {
            return 0;
        }

        withdrawAmount = withdrawAmount - _solelyWithdrawAmount;

        if (withdrawAmount > maxAmountPool) {
            return maxAmountPool;
        }

        return withdrawAmount;
    }

    /**
     * @dev View function extrapolating the
     * possible withdraw amount of a private
     * postion for a specific _poolToken.
     */
    function maximumWithdrawTokenSolely(
        uint256 _nftId,
        address _poolToken,
        uint256 _interval,
        uint256 _poolWithdrawAmount
    )
        external
        view
        returns (uint256)
    {
        uint256 tokenAmount = _getPossibleWithdrawAmount(
            _nftId,
            _poolToken,
            _interval
        );

        if (_isDecollateralized(_nftId, _poolToken) == false) {

            if (_poolWithdrawAmount >= tokenAmount) {
                return 0;
            }

            tokenAmount = tokenAmount - _poolWithdrawAmount;
        }

        uint256 maxSolelyAmount = WISE_LENDING.getPureCollateralAmount(
            _nftId,
            _poolToken
        );

        if (tokenAmount > maxSolelyAmount) {
            return maxSolelyAmount;
        }

        return tokenAmount;
    }

    /**
     * @dev View function extrapolating the
     * possible borrow amount of postion for
     * a specific _poolToken.
     */
    function maximumBorrowToken(
        uint256 _nftId,
        address _poolToken,
        uint256 _interval
    )
        external
        view
        returns (uint256 tokenAmount)
    {
        uint256 term = _overallUSDCollateralsWeighted(_nftId, _interval)
            * borrowPercentageCap
            / PRECISION_FACTOR_E18;

        uint256 borrowUSD = term
            - _overallUSDBorrow(
                _nftId,
                _interval
            );

        tokenAmount = WISE_ORACLE.getTokensFromUSD(
            _poolToken,
            borrowUSD
        );

        uint256 maxPoolAmount = WISE_LENDING.getTotalPool(
            _poolToken
        );

        if (tokenAmount > maxPoolAmount) {
            tokenAmount = maxPoolAmount;
        }
    }

    /**
     * @dev View function extrapolating the
     * possible payback amount of a position
     * for a specific _poolToken.
     */
    function getExpectedPaybackAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _interval
    )
        external
        view
        returns (uint256)
    {
        uint256 borrowShares = WISE_LENDING.getPositionBorrowShares(
            _nftId,
            _poolToken
        );

        uint256 currentTotalBorrowShares = WISE_LENDING.getTotalBorrowShares(
            _poolToken
        );

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

        uint256 updatedPseudo = _getUpdatedPseudoBorrow(
            _poolToken,
            _interval
        );

        return borrowShares
            * updatedPseudo
            / currentTotalBorrowShares;
    }

    /**
     * @dev View function extrapolating the
     * possible lending amount of a position
     * for a specific _poolToken.
     */
    function getExpectedLendingAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _interval
    )
        public
        view
        returns (uint256)
    {
        uint256 lendingShares = WISE_LENDING.getPositionLendingShares(
            _nftId,
            _poolToken
        );

        uint256 currentTotalLendingShares = WISE_LENDING.getTotalDepositShares(
            _poolToken
        );

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

        uint256 updatedPseudo = _getUpdatedPseudoPool(
            _poolToken,
            _interval
        );

        return lendingShares
            * updatedPseudo
            / currentTotalLendingShares;
    }

    /**
     * @dev Set function for blacklisting token.
     * Those token can not be borrowed our used as
     * collateral anymore. Only callable by master.
     */
    function setBlacklistToken(
        address _tokenAddress,
        bool _state
    )
        external
        onlyMaster()
    {
        wasBlacklisted[_tokenAddress] = _state;
    }
}

// File: contracts/TransferHub/ApprovalHelper.sol



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

pragma solidity =0.8.21;

import "./CallOptionalReturn.sol";

contract ApprovalHelper is CallOptionalReturn {

    bytes4 constant approve = IERC20
        .approve
        .selector;

    /**
     * @dev
     * Allows to execute safe approve for a token
     */
    function _safeApprove(
        address _token,
        address _spender,
        uint256 _value
    )
        internal
    {
        _callOptionalReturn(
            _token,
            abi.encodeWithSelector(
                approve,
                _spender,
                _value
            )
        );
    }
}
// File: contracts/WiseSecurity/WiseSecurityHelper.sol



// SPDX-License-Identifier: -- WISE --
pragma solidity =0.8.21;

import "./WiseSecurityDeclarations.sol";

abstract contract WiseSecurityHelper is WiseSecurityDeclarations {

     /**
     * @dev Read function returning weighted and
     * and unweighted total collateral of a
     * postion with {_nftId} (unweighted means
     * collateral factor equals 1E18).
     */
    function overallUSDCollateralsBoth(
        uint256 _nftId
    )
        public
        view
        returns (uint256, uint256)
    {
        uint256 amount;
        uint256 weightedTotal;
        uint256 unweightedAmount;
        address tokenAddress;

        for (uint256 i = 0; i < WISE_LENDING.getPositionLendingTokenLength(_nftId); ++i) {

            tokenAddress = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            if (checkHeartbeat(tokenAddress) == false) {
                revert ChainlinkDead();
            }

            amount = getFullCollateralUSD(
                _nftId,
                tokenAddress
            );

            weightedTotal += amount
                * WISE_LENDING.lendingPoolData(tokenAddress).collateralFactor
                / PRECISION_FACTOR_E18;

            unweightedAmount += amount;
        }

        return (
            weightedTotal,
            unweightedAmount
        );
    }

    /**
     * @dev Read function returning weighted
     *  total collateral of a postion with {_nftId}.
     */
    function overallUSDCollateralsWeighted(
        uint256 _nftId
    )
        public
        view
        returns (uint256 weightedTotal)
    {
        address tokenAddress;

        for (uint256 i = 0; i < WISE_LENDING.getPositionLendingTokenLength(_nftId); ++i) {

            tokenAddress = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            _tokenChecks(
                tokenAddress
            );

            weightedTotal += WISE_LENDING.lendingPoolData(tokenAddress).collateralFactor
                * getFullCollateralUSD(
                    _nftId,
                    tokenAddress
                ) / PRECISION_FACTOR_E18;
        }
    }

    /**
     * @dev Read function returning unweighted
     *  total collateral of a postion with {_nftId}
     * (unweighted means collateral factor equals 1E18).
     */
    function overallUSDCollateralsBare(
        uint256 _nftId
    )
        public
        view
        returns (uint256 amount)
    {
        address tokenAddress;

        for (uint256 i = 0; i < WISE_LENDING.getPositionLendingTokenLength(_nftId); ++i) {

            tokenAddress = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            if (checkHeartbeat(tokenAddress) == false) {
                revert ChainlinkDead();
            }

            amount += getFullCollateralUSD(
                _nftId,
                tokenAddress
            );
        }
    }

    /**
     * @dev Internal calculation function returning
     * the updated weighted collateral amount of a
     * postion with {_nftId}. Result can be
     * extrapolated linear with length {_interval}.
     */
    function _overallUSDCollateralsWeighted(
        uint256 _nftId,
        uint256 _interval
    )
        internal
        view
        returns (uint256 weightedTotal)
    {
        uint256 i;
        address tokenAddress;

        for (i = 0; i < WISE_LENDING.getPositionLendingTokenLength(_nftId); ++i) {

            tokenAddress = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            _tokenChecks(
                tokenAddress
            );

            weightedTotal += WISE_LENDING.lendingPoolData(tokenAddress).collateralFactor
                * _getCollateralOfTokenUSDUpdated(
                    _nftId,
                    tokenAddress,
                    _interval
                ) / PRECISION_FACTOR_E18;
        }
    }

    /**
     * @dev Read function returning the full bare
     * collateral amount of a {_poolToken} from a
     * {_nftId}. Full means sum of private and
     * public added funds.
     */
    function getFullCollateralUSD(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (uint256 usdCollateral)
    {
        usdCollateral = WISE_ORACLE.getTokensInUSD(
            _poolToken,
            WISE_LENDING.getPureCollateralAmount(
                _nftId,
                _poolToken
            )
        );

        if (_isDecollateralized(_nftId, _poolToken) == true) {
            return usdCollateral;
        }

        usdCollateral += getUSDCollateral(
            _nftId,
            _poolToken
        );
    }

    /**
     * @dev Wrapper function checking if a supplied
     * fund is decollateralized.
     */
    function _isDecollateralized(
        uint256 _nftId,
        address _poolToken
    )
        internal
        view
        returns (bool)
    {
        return WISE_LENDING.isDecollteralized(
            _nftId,
            _poolToken
        );
    }

    /**
     * @dev Read function returning the full
     * collateral amount of a {_poolToken} from a
     * {_nftId} updated to current values.
     * Full means sum of private and public added
     * funds. Can be extrapolated linear within
     * {_interval}.
     */
    function _getCollateralOfTokenUSDUpdated(
        uint256 _nftId,
        address _poolToken,
        uint256 _interval
    )
        internal
        view
        returns (uint256 usdCollateral)
    {
        usdCollateral = WISE_ORACLE.getTokensInUSD(
            _poolToken,
            WISE_LENDING.getPureCollateralAmount(
                _nftId,
                _poolToken
            )
        );

        if (_isDecollateralized(_nftId, _poolToken) == true) {
            return usdCollateral;
        }

        usdCollateral += getUSDCollateralUpdated(
            _nftId,
            _poolToken,
            _interval
        );
    }

    /**
     * @dev Read function returning the full
     * (private and public) collateral amount
     * of a {_poolToken} from a {_nftId} updated
     * to current values. Can be extrapolated
     * linear within {_interval}.
     */
    function getUSDCollateralUpdated(
        uint256 _nftId,
        address _poolToken,
        uint256 _interval
    )
        public
        view
        returns (uint256)
    {
        uint256 lendingShares = WISE_LENDING.getPositionLendingShares(
            _nftId,
            _poolToken
        );

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

        uint256 currentTotalLendingShares = WISE_LENDING.getTotalDepositShares(
            _poolToken
        );

        uint256 updatedPseudo = _getUpdatedPseudoPool(
            _poolToken,
            _interval
        );

        uint256 updatedToken = lendingShares
            * updatedPseudo
            / currentTotalLendingShares;

        return WISE_ORACLE.getTokensInUSD(
            _poolToken,
            updatedToken
        );
    }

    /**
     * @dev Read function returning the public
     * collateral amount of a {_poolToken} from a
     * {_nftId}.
     */
    function getUSDCollateral(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return WISE_ORACLE.getTokensInUSD(
            _poolToken,
            getPositionLendingAmount(
                _nftId,
                _poolToken
            )
        );
    }

    /**
     * @dev Read function returning the total
     * borrow amount of a postion with {_nftId}.
     * No heartbeat or blacklisted checks are
     * included in this function!
     */
    function _overallUSDBorrowBare(
        uint256 _nftId
    )
        internal
        view
        returns (uint256 buffer)
    {
        for (uint256 i = 0; i < WISE_LENDING.getPositionBorrowTokenLength(_nftId); ++i) {

            buffer += getUSDBorrow(
                _nftId,
                WISE_LENDING.getPositionBorrowTokenByIndex(
                    _nftId,
                    i
                )
            );
        }
    }

    /**
     * @dev Read function returning the total
     * borrow amount of a postion with {_nftId}.
     * No blacklisted check is included
     * in this function!
     */
    function overallUSDBorrowHeartbeat(
        uint256 _nftId
    )
        public
        view
        returns (uint256 buffer)
    {
        address tokenAddress;

        for (uint256 i = 0; i < WISE_LENDING.getPositionBorrowTokenLength(_nftId); ++i) {

            tokenAddress = WISE_LENDING.getPositionBorrowTokenByIndex(
                _nftId,
                i
            );

            if (checkHeartbeat(tokenAddress) == false) {
                revert ChainlinkDead();
            }

            buffer += getUSDBorrow(
                _nftId,
                tokenAddress
            );
        }
    }

    /**
     * @dev Read function returning the total
     * borrow amount of a postion with {_nftId}.
     */
    function overallUSDBorrow(
        uint256 _nftId
    )
        public
        view
        returns (uint256 buffer)
    {
        uint256 i;
        address tokenAddress;

        for (i = 0; i < WISE_LENDING.getPositionBorrowTokenLength(_nftId); ++i) {

            tokenAddress = WISE_LENDING.getPositionBorrowTokenByIndex(
                _nftId,
                i
            );

            _tokenChecks(
                tokenAddress
            );

            buffer += getUSDBorrow(
                _nftId,
                tokenAddress
            );
        }
    }

    /**
     * @dev Internal function combining hearbeat
     * and blacklisted checks.
     */
    function _checkConditions(
        address _poolToken
    )
        internal
        view
        returns (bool)
    {
        return checkHeartbeat(_poolToken) == false
            || wasBlacklisted[_poolToken] == true;
    }

    /**
     * @dev Read function returning the total
     * updated current borrow amount of a
     * postion with {_nftId}. Can be
     * extrapolated linear with {_intervall}.
     */
    function _overallUSDBorrow(
        uint256 _nftId,
        uint256 _interval
    )
        internal
        view
        returns (uint256 buffer)
    {
        uint256 i;
        address tokenAddress;

        for (i = 0; i < WISE_LENDING.getPositionBorrowTokenLength(_nftId); ++i) {

            tokenAddress = WISE_LENDING.getPositionBorrowTokenByIndex(
                _nftId,
                i
            );

            _tokenChecks(
                tokenAddress
            );

            buffer += _getUSDBorrowUpdated(
                _nftId,
                tokenAddress,
                _interval
            );
        }
    }

    /**
     * @dev Internal function calculating
     * the updated pseudo borrow amount of
     * {_poolToken}. Result can be extrapolated
     * linear with {_interval}.
     */
    function _getUpdatedPseudoBorrow(
        address _poolToken,
        uint256 _interval
    )
        internal
        view
        returns (uint256)
    {
        uint256 currentPseudo = WISE_LENDING.getPseudoTotalBorrowAmount(
            _poolToken
        );

        return _getInterest(
            _poolToken,
            _interval
        ) + currentPseudo;
    }

    /**
     * @dev Internal function calculating
     * the updated pseudo lending amount of
     * {_poolToken}. Result can be extrapolated
     * linear with {_interval}.
     */
    function _getUpdatedPseudoPool(
        address _poolToken,
        uint256 _interval
    )
        internal
        view
        returns (uint256)
    {
        uint256 currentPseudo = WISE_LENDING.getPseudoTotalPool(
            _poolToken
        );

        return _getInterest(_poolToken, _interval)
            * (PRECISION_FACTOR_E18 - WISE_LENDING.globalPoolData(_poolToken).poolFee)
            / PRECISION_FACTOR_E18
            + currentPseudo;
    }

    /**
     * @dev Internal math function calculating
     * the accumulated interest amount for
     * {_poolToken}. Result can be extrapolated
     * linear with {_interval}.
     */
    function _getInterest(
        address _poolToken,
        uint256 _interval
    )
        internal
        view
        returns (uint256)
    {
        BorrowPoolEntry memory borrowPoolData = WISE_LENDING.borrowPoolData(
            _poolToken
        );

        uint256 timeInterval = _interval
            + block.timestamp
            - WISE_LENDING.getTimeStamp(_poolToken);

        uint256 rate = timeInterval
            * borrowPoolData.borrowRate
            * WISE_LENDING.getPseudoTotalBorrowAmount(_poolToken)
            / PRECISION_FACTOR_E18
            / ONE_YEAR;

        return rate;
    }

    /**
     * @dev Read function returning the full
     * borrow amount of a {_poolToken} from a
     * {_nftId} updated to current values.
     * Can be extrapolated linear within
     * {_interval}.
     */
    function _getUSDBorrowUpdated(
        uint256 _nftId,
        address _poolToken,
        uint256 _intervall
    )
        internal
        view
        returns (uint256)
    {
        uint256 borrowShares = WISE_LENDING.getPositionBorrowShares(
            _nftId,
            _poolToken
        );

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

        uint256 currentTotalBorrowShares = WISE_LENDING.getTotalBorrowShares(
            _poolToken
        );

        uint256 updatesPseudo = _getUpdatedPseudoBorrow(
            _poolToken,
            _intervall
        );

        uint256 updatedToken = borrowShares
            * updatesPseudo
            / currentTotalBorrowShares;

        return WISE_ORACLE.getTokensInUSD(
            _poolToken,
            updatedToken
        );
    }

    /**
     * @dev Read function returning the borow
     * amount of a {_poolToken} from a {_nftId}.
     */
    function getUSDBorrow(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return WISE_ORACLE.getTokensInUSD(
            _poolToken,
            getPositionBorrowAmount(
                _nftId,
                _poolToken
            )
        );
    }

    /**
     * @dev Read function checking if
     * {_poolToken} is allowed to borrow.
     */
    function checkTokenAllowed(
        address _poolAddress
    )
        public
        view
    {
        if (WISE_LENDING.borrowPoolData(_poolAddress).allowBorrow == false) {
            revert NotAllowedToBorrow();
        }
    }

    /**
     * @dev Check if chainLink feed was
     * updated within expected timeframe
     */
    function checkHeartbeat(
        address _poolToken
    )
        public
        view
        returns (bool)
    {
        if (WISE_ORACLE.chainLinkIsDead(_poolToken) == true) {
            return false;
        }

        return true;
    }

    /**
     * @dev Check if the postion with
     * {_nftId} is locked for interactions.
     */
    function _checkPositionLocked(
        uint256 _nftId
    )
        internal
        view
    {
        if (WISE_LENDING.positionLocked(_nftId) == true) {
            revert PositionLocked();
        }
    }

    /**
     * @dev Wrapper function for external
     * {_checkMaxFee} call.
     */
    function checkMaxFee(
        uint256 _paybackUSD,
        uint256 _feeLiquidation,
        uint256 _maxFeeUSD
    )
        external
        pure
        returns (uint256)
    {
        return _checkMaxFee(
            _paybackUSD,
            _feeLiquidation,
            _maxFeeUSD
        );
    }

    /**
     * @dev Returning the possible fee
     * for liquidation.
     */
    function _checkMaxFee(
        uint256 _paybackUSD,
        uint256 _liquidationFee,
        uint256 _maxFeeUSD
    )
        internal
        pure
        returns (uint256)
    {
        uint256 feeUSD = _paybackUSD
            * _liquidationFee
            / PRECISION_FACTOR_E18;

        return feeUSD < _maxFeeUSD
            ? feeUSD
            : _maxFeeUSD;
    }

    /**
     * @dev Math function computing the
     * percentage of the receiving token
     * which the liquidator receivs for
     * liquidation.
     */
    function calculateWishPercentage(
        uint256 _nftId,
        address _receiveToken,
        uint256 _paybackUSD,
        uint256 _maxFeeUSD,
        uint256 _baseRewardLiquidation
    )
        external
        view
        returns (uint256)
    {
        uint256 feeUSD = _checkMaxFee(
            _paybackUSD,
            _baseRewardLiquidation,
            _maxFeeUSD
        );

        return (feeUSD + _paybackUSD)
            * PRECISION_FACTOR_E18
            / getFullCollateralUSD(
                _nftId,
                _receiveToken
            );
    }

    /**
     * @dev Check function for withdraw flow.
     * Tests if debt ratio is not greater than
     * 100% after withdraw of {_poolToken} for
     * {_amount}.
     */
    function checkBorrowLimit(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        public
        view
    {
        uint256 borrowAmount = overallUSDBorrow(
            _nftId
        );

        if (borrowAmount == 0) {
            return;
        }

        uint256 withdrawValue = WISE_ORACLE.getTokensInUSD(
            _poolToken,
            _amount
        )
            * WISE_LENDING.lendingPoolData(_poolToken).collateralFactor
            / PRECISION_FACTOR_E18;

        bool state = borrowPercentageCap
            * (overallUSDCollateralsWeighted(_nftId) - withdrawValue)
            / PRECISION_FACTOR_E18
            < borrowAmount;

        if (state == true) {
            revert ResultsInBadDebt();
        }
    }

    /**
     * @dev Check function for borrow flow.
     * Tests if debt ratio is not greater than
     * 100% after borrow of {_poolToken} for
     * {_amount}.
     */
    function _checkBorrowPossible(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        internal
        view
    {
        uint256 borrowValue = WISE_ORACLE.getTokensInUSD(
            _poolToken,
            _amount
        );

        bool state = borrowPercentageCap
            * overallUSDCollateralsWeighted(_nftId)
            / PRECISION_FACTOR_E18
            < overallUSDBorrow(_nftId) + borrowValue;

        if (state == true) {
            revert NotEnoughCollateral();
        }
    }

    /**
     * @dev Check function for registration
     * of power farms. User can only register
     * when the postion is empty!
     */
    function checksRegister(
        uint256 _nftId,
        address _caller
    )
        public
        view
    {
        checkOwnerPosition(
            _nftId,
            _caller
        );

        if (overallUSDCollateralsWeighted(_nftId) > 0) {
            revert NotAllowedWiseSecurity();
        }
    }

    /**
     * @dev Pure math function comparing
     * borrow and collateral amount for
     * liquidation.
     */
    function canLiquidate(
        uint256 _borrowUSDTotal,
        uint256 _weightedCollateralUSD
    )
        public
        pure
    {
        if (_borrowUSDTotal < _weightedCollateralUSD) {
            revert LiquidationDenied();
        }
    }

    /**
     * @dev Helper function for liquidation checking
     * return amount of receiving shares for liquidator.
     * Has to be smaller 50% when no bad debt occurs.
     */
    function checkMaxShares(
        uint256 _nftId,
        address _tokenToPayback,
        uint256 _borrowUSDTotal,
        uint256 _unweightedCollateralUSD,
        uint256 _shareAmountToPay
    )
        public
        view
    {
        uint256 totalSharesUser = WISE_LENDING.getPositionBorrowShares(
            _nftId,
            _tokenToPayback
        );

        uint256 maxShares = checkBadDebtThreshold(_borrowUSDTotal, _unweightedCollateralUSD)
            ? totalSharesUser
            : totalSharesUser * MAX_LIQUIDATION_50 / PRECISION_FACTOR_E18;

        if (_shareAmountToPay <= maxShares) {
            return;
        }

        revert TooManyShares();
    }

    /**
     * @dev Helper function for liquidation checking
     * if postion has bad debt.
     */
    function checkBadDebtThreshold(
        uint256 _borrowUSDTotal,
        uint256 _unweightedCollateral
    )
        public
        pure
        returns (bool)
    {
        return _borrowUSDTotal * PRECISION_FACTOR_E18
            >= _unweightedCollateral * BAD_DEBT_THRESHOLD;
    }

    /**
     * @dev Helper function computing lending
     * token amount for {_poolToken}.
     */
    function getPositionLendingAmount(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return WISE_LENDING.cashoutAmount(
            _poolToken,
            WISE_LENDING.getPositionLendingShares(
                _nftId,
                _poolToken
            )
        );
    }

    /**
     * @dev Helper function computing borrow
     * token amount for {_poolToken}.
     */
    function getPositionBorrowAmount(
        uint256 _nftId,
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return WISE_LENDING.paybackAmount(
            _poolToken,
            WISE_LENDING.getPositionBorrowShares(
                _nftId,
                _poolToken
            )
        );
    }

    /**
     * @dev Helper function checking the owner
     * of {_nftId}. Is skipped when aaveHub or
     * power farm is calling the function.
     */
    function checkOwnerPosition(
        uint256 _nftId,
        address _caller
    )
        public
        view
    {
        if (POSITION_NFTS.reserved(_caller) == _nftId) {
            return;
        }

        if (POSITION_NFTS.ownerOf(_nftId) == _caller) {
            return;
        }

        revert NotOwner();
    }

    /**
     * @dev Wrapper function returning the borrow
     * rate from pool with token {_poolToken}.
     */
    function getBorrowRate(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        return WISE_LENDING.borrowPoolData(_poolToken).borrowRate;
    }

    /**
     * @dev View function returning the lending
     * rate from pool with token {_poolToken}.
     */
    function getLendingRate(
        address _poolToken
    )
        public
        view
        returns (uint256)
    {
        uint256 pseudoTotalPool = WISE_LENDING.getPseudoTotalPool(
            _poolToken
        );

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

        uint256 adjustedRate = getBorrowRate(_poolToken)
            * (PRECISION_FACTOR_E18 - WISE_LENDING.globalPoolData(_poolToken).poolFee)
            / PRECISION_FACTOR_E18;

        return adjustedRate
            * WISE_LENDING.getPseudoTotalBorrowAmount(_poolToken)
            / pseudoTotalPool;
    }

    /**
     * @dev Internal helper function calculating
     * the possible withdraw amount of {_poolToken}
     * under current borrow and collateral amount
     * of {_nftId}.
     */
    function _getPossibleWithdrawAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _interval
    )
        internal
        view
        returns (uint256)
    {
        uint256 term = _overallUSDBorrow(_nftId, _interval)
            * PRECISION_FACTOR_E18
            / borrowPercentageCap;

        uint256 withdrawUSD = PRECISION_FACTOR_E18
            * (_overallUSDCollateralsWeighted(_nftId, _interval) - term)
            / WISE_LENDING.lendingPoolData(_poolToken).collateralFactor;

        return WISE_ORACLE.getTokensFromUSD(
            _poolToken,
            withdrawUSD
        );
    }

    /**
     * @dev Wrapper for {_checkConditions}.
     */
    function _tokenChecks(
        address _poolToken
    )
        internal
        view
    {
        if (_checkConditions(_poolToken) == true) {
            revert();
        }
    }

    /**
     * @dev Internal helper checking of success
     * for a low level byte call of a function
     * with {.call()}.
     */
    function _checkSuccess(
        bool _success
    )
        internal
        pure
    {
        if (_success == false) {
            revert SecuritySwapFaild();
        }
    }
}

// File: contracts/WiseSecurity/WiseSecurityDeclarations.sol



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

pragma solidity =0.8.21;

import "../InterfaceHub/IERC20.sol";
import "../InterfaceHub/ICurve.sol";
import "../InterfaceHub/IPositionNFTs.sol";
import "../InterfaceHub/IWiseOracleHub.sol";
import "../InterfaceHub/IFeeManager.sol";
import "../InterfaceHub/IWiseLending.sol";
import "../InterfaceHub/IWiseLiquidation.sol";
import "../InterfaceHub/IAaveHub.sol";

import "../FeeManager/FeeManager.sol";
import "../OwnableMaster.sol";

error NotAllowedWiseSecurity();
error ChainlinkDead();
error PositionLocked();
error ResultsInBadDebt();
error DepositCapReached();
error NotEnoughCollateral();
error NotAllowedToBorrow();
error OpenBorrowPosition();
error NonVerifiedPool();
error NotOwner();
error LiquidationDenied();
error TooManyShares();
error NotRegistered();
error Blacklisted();
error SecuritySwapFaild();

contract WiseSecurityDeclarations is OwnableMaster {

    constructor(
        address _master,
        address _wiseLendingAddress,
        address _aaveHubAddress,
        uint256 _borrowPercentageCap
    )
        OwnableMaster(
            _master
        )
    {
        WISE_LENDING = IWiseLending(
            _wiseLendingAddress
        );

        AAVE_HUB = _aaveHubAddress;

        address master = WISE_LENDING.master();
        address oracleHubAddress = WISE_LENDING.WISE_ORACLE();
        address positionNFTAddress = WISE_LENDING.POSITION_NFT();

        FeeManager feeManagerContract = new FeeManager(
            master,
            IAaveHub(AAVE_HUB).AAVE_ADDRESS(),
            _wiseLendingAddress,
            oracleHubAddress,
            address(this),
            positionNFTAddress
        );

        WISE_ORACLE = IWiseOracleHub(
            oracleHubAddress
        );

        FEE_MANAGER = IFeeManager(
            address(feeManagerContract)
        );

        WISE_LIQUIDATION = IWiseLiquidation(
            _wiseLendingAddress
        );

        POSITION_NFTS = IPositionNFTs(
            positionNFTAddress
        );

        borrowPercentageCap = _borrowPercentageCap;

        baseRewardLiquidation = 10 * PRECISION_FACTOR_E16;
        baseRewardLiquidationFarm = 3 * PRECISION_FACTOR_E16;

        maxFeeUSD = 50000 * PRECISION_FACTOR_E18;
        maxFeeFarmUSD = 50000 * PRECISION_FACTOR_E18;
    }

    // ---- Variables ----

    uint256 public borrowPercentageCap;
    address public immutable AAVE_HUB;

    // ---- Interfaces ----

    // Interface feeManager contract
    IFeeManager public immutable FEE_MANAGER;

    // Interface wiseLending contract
    IWiseLending public immutable WISE_LENDING;

    // Interface position NFT contract
    IPositionNFTs public immutable POSITION_NFTS;

    // Interface oracleHub contract
    IWiseOracleHub public immutable WISE_ORACLE;

    // Interface wiseLiquidation contract
    IWiseLiquidation public immutable WISE_LIQUIDATION;

    // Interface for zero interface
    ICurve constant ZERO_CURVE = ICurve(
        address(0x0)
    );

    // Threshold values
    uint256 constant MAX_LIQUIDATION_50 = 50E16;
    uint256 constant BAD_DEBT_THRESHOLD = 89E16;

    uint256 constant TARGET_DEC = 18;
    uint256 constant UINT256_MAX = type(uint256).max;
    uint256 constant ONE_YEAR = 52 weeks;

    // Precision factors for computations
    uint256 constant PRECISION_FACTOR_E16 = 1E16;
    uint256 constant PRECISION_FACTOR_E18 = 1E18;
    uint256 constant PRECISION_FACTOR_E36 = PRECISION_FACTOR_E18 * PRECISION_FACTOR_E18;


    // ---- Mappings ----

    // Mapping pool token to blacklist bool
    mapping(address => bool) public wasBlacklisted;

    // Mapping basic swap data for s curve swaps to pool token
    mapping(address => CurveSwapStructData) public curveSwapInfoData;

    // Mapping swap info of swap token for reentrency guard to pool token
    mapping(address => CurveSwapStructToken) public curveSwapInfoToken;

    // ---- Liquidation Variables ----

    // @TODO - store all 4 in a struct:

    // Base reward for liquidator normal liquidation
    uint256 public baseRewardLiquidation;

    // Base reward for liquidator power farm liquidation
    uint256 public baseRewardLiquidationFarm;

    // Max reward USD for liquidator power farm liquidation
    uint256 public maxFeeUSD;

    // Max reward USD for liquidator normal liquidation
    uint256 public maxFeeFarmUSD;
}

// File: contracts/TransferHub/CallOptionalReturn.sol



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

pragma solidity =0.8.21;

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

error CallFailed();

contract CallOptionalReturn {

    /**
     * @dev
     * Helper function to do low-level call
     */
    function _callOptionalReturn(
        address token,
        bytes memory data
    )
        internal
        returns (bool call)
    {
        (
            bool success,
            bytes memory returndata
        ) = token.call(
            data
        );

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

        call = success
            && results
            && token.code.length > 0;

        if (call == false) {
            revert CallFailed();
        }
    }
}
// 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();
    }

    constructor(
        address _master
    ) {
        master = _master;
    }

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

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

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



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

pragma solidity =0.8.21;

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

import "./FeeManagerHelper.sol";

/**
 * @dev Purpose of this contract is to organize fee distribution from wiseLending.
 * The feeManager aquires fee token in form of shares from each pool and can call them
 * with "claimWiseFees()" for each pool.
 *
 * Furthermore, this contracts has two different incentive
 * structures which can be used to bootstrap the WISE ecosystem (beneficial and incnetiveOwner roles).
 *
 * Additionally, this contract keeps track of the bad debt of each postion and has a simple mechanism
 * to pay them back via incentives. The incentive amount is funded by the gathered fees.
 */

contract FeeManager is FeeManagerHelper {

    constructor(
        address _master,
        address _aaveAddress,
        address _wiseLendingAddress,
        address _oracleHubAddress,
        address _wiseSecurityAddress,
        address _positionNFTAddress
    )
        DeclarationsFeeManager(
            _master,
            _aaveAddress,
            _wiseLendingAddress,
            _oracleHubAddress,
            _wiseSecurityAddress,
            _positionNFTAddress
        )
    {}

    /**
     * @dev Allows to adjust the paid out incentive
     * percentage for user to reduce bad debt.
     */
    function setRepayBadDebtIncentive(
        uint256 _percent
    )
        external
        onlyMaster
    {
        _checkValue(
            _percent
        );

        paybackIncentive = _percent;
    }

    /**
     * @dev Maps underlying token with corresponding aToken.
     * Sets bool to identify pool token as aToken.
     */
    function setAaveFlag(
        address _poolToken,
        address _underlyingToken
    )
        external
        onlyMaster
    {
        isAaveToken[_poolToken] = true;
        underlyingToken[_poolToken] = _underlyingToken;
    }

    /**
     * @dev Function to adjust pool fee. Fee can not be greater than 100%
     * or lower than 1%. Can be adjusted for each pool individually.
     */
    function setPoolFee(
        address _poolToken,
        uint256 _newFee
    )
        external
        onlyMaster
    {
        _checkValue(
            _newFee
        );

        WISE_LENDING.setPoolFee(
            _poolToken,
            _newFee
        );
    }

    /**
     * @dev Function to propose new incentive master. This role can increase
     * the incentive amount for both incentive mappings. These are two roles
     * for incentivising external persons e.g. developers.
     */
    function proposeIncentiveMaster(
        address _proposedIncentiveMaster
    )
        external
        onlyIncentiveMaster
    {
        proposedIncentiveMaster = _proposedIncentiveMaster;
    }

    /**
     * @dev Claim proposed incentive master by proposed entity.
     */
    function claimOwnershipIncentiveMaster()
        external
    {
        if (msg.sender != proposedIncentiveMaster) {
            revert NotAllowed();
        }

        incentiveMaster = proposedIncentiveMaster;
        proposedIncentiveMaster = ZERO_ADDRESS;
    }

    /**
     * @dev Increase function for increasing incentive amount for entity A.
     * Only callable by incentive master.
     */
    function increaseIncentiveA(
        uint256 _value
    )
        external
        onlyIncentiveMaster
    {
        incentiveUSD[incentiveOwnerA] += _value;
    }

    /**
     * @dev Increase function for increasing incentive amount for entity B.
     * Only callable by incentive master.
     */
    function increaseIncentiveB(
        uint256 _value
    )
        external
        onlyIncentiveMaster
    {
        incentiveUSD[incentiveOwnerB] += _value;
    }

    /**
     * @dev Function to claim all gathered incetives.
     */
    function claimIncentivesBulk()
        external
    {
        address tokenAddress;

        for (uint8 i = 0; i < getPoolTokenAddressesLength(); ++i) {

            tokenAddress = poolTokenAddresses[i];

            if (isAaveToken[tokenAddress] == true) {
                tokenAddress = underlyingToken[tokenAddress];
            }

            claimIncentives(
                tokenAddress
            );
        }
    }

    /**
     * @dev Claims gathered incentives for a specific token.
     */
    function claimIncentives(
        address _feeToken
    )
        public
    {
        _safeTransfer(
            _feeToken,
            msg.sender,
            gatheredIncentiveToken[msg.sender][_feeToken]
        );

        delete gatheredIncentiveToken[msg.sender][_feeToken];
    }

    /**
     * @dev Function chaning incentiveOwnerA!. Only callable by
     * incentiveOwnerA.
     */
    function changeIncentiveUSDA(
        address _newOwner
    )
        external
    {
        if (msg.sender != incentiveOwnerA) {
            revert NotAllowed();
        }

        incentiveUSD[_newOwner] = incentiveUSD[
            incentiveOwnerA
        ];

        delete incentiveUSD[
            incentiveOwnerA
        ];

        incentiveOwnerA = _newOwner;
    }

    /**
     * @dev Function chaning incentiveOwnerB!. Only callable by
     * incentiveOwnerB.
     */
    function changeIncentiveUSDB(
        address _newOwner
    )
        external
    {
        if (msg.sender != incentiveOwnerB) {
            revert NotAllowed();
        }

        incentiveUSD[_newOwner] = incentiveUSD[
            incentiveOwnerB
        ];

        delete incentiveUSD[incentiveOwnerB];

        incentiveOwnerB = _newOwner;
    }

    /**
     * @dev Function adding new pool token to pool token list.
     * Called during pool creation and only callable by wiseLending
     * contract.
     */
    function addPoolTokenAddress(
        address _poolToken
    )
        external
        onlyWiseLending
    {
        poolTokenAddresses.push(
            _poolToken
        );

        poolTokenAdded[_poolToken] = true;

        emit PoolTokenAdded(
            _poolToken,
            block.timestamp
        );
    }

    /**
     * @dev Function to add pool token manualy. Only
     * callable by feeManager master.
     */
    function addPoolTokenAddressManual(
        address _poolToken
    )
        external
        onlyMaster
    {
        if (poolTokenAdded[_poolToken] == true) {
            revert PoolAlreadyAdded();
        }

        poolTokenAddresses.push(
            _poolToken
        );

        poolTokenAdded[_poolToken] = true;

        emit PoolTokenAdded(
            _poolToken,
            block.timestamp
        );
    }

    /**
     * @dev Function to remove pool token manualy from pool
     * token list. Only callable by feeManager master.
     */
    function removePoolTokenManual(
        address _poolToken
    )
        external
        onlyMaster
    {
        uint256 len = getPoolTokenAddressesLength();
        uint256 lastEntry = len - 1;

        for (uint8 i = 0; i < len; ++i) {

            if (_poolToken != poolTokenAddresses[i]) {
                continue;
            }

            poolTokenAddresses[i] = poolTokenAddresses[lastEntry];
            poolTokenAddresses.pop();
            poolTokenAdded[_poolToken] = false;

            break;
        }
    }

    /**
     * @dev Increase function for total bad debt of
     * wiseLending. Only callable by wiseSecurity contract
     * during liquidation.
     */
    function increaseTotalBadDebtLiquidation(
        uint256 _amount
    )
        external
        onlyWiseSecurity
    {
        _increaseTotalBadDebt(
            _amount
        );

        emit BadDebtIncreasedLiquidation(
            _amount,
            block.timestamp
        );
    }

    /**
     * @dev Increase function for bad debt of a position.
     * Only callable by wiseSecurity contract during liquidation.
     */
    function setBadDebtUserLiquidation(
        uint256 _nftId,
        uint256 _amount
    )
        external
        onlyWiseSecurity
    {
        _setBadDebtPosition(
            _nftId,
            _amount
        );

        emit SetBadDebtPosition(
            _nftId,
            _amount,
            block.timestamp
        );
    }

    /**
     * @dev Set function to declare an address as beneficial for
     * a fee token. Address can claim gathered fee token as long as
     * it is declared as beneficial. Only setable by master.
     */
    function setBeneficial(
        address _user,
        address[] memory _feeTokens
    )
        external
        onlyMaster
    {
        for (uint8 i = 0; i < _feeTokens.length; ++i) {
            _setAllowedTokens(
                _user,
                _feeTokens[i],
                true
            );
        }

        emit SetBeneficial(
            _user,
            _feeTokens,
            block.timestamp
        );
    }

    /**
     * @dev Set function to remove an address as beneficial for
     * a fee token. Only setable by master.
     */
    function revokeBeneficial(
        address _user,
        address[] memory _feeTokens
    )
        external
        onlyMaster
    {
        for (uint8 i = 0; i < _feeTokens.length; ++i) {
            _setAllowedTokens(
                _user,
                _feeTokens[i],
                false
            );
        }

        emit RevokeBeneficial(
            _user,
            _feeTokens,
            block.timestamp
        );
    }

    /**
     * @dev Claim all fees from wiseLending and send them to feeManager.
     */
    function claimWiseFeesBulk()
        external
    {
        for (uint8 i = 0; i < getPoolTokenAddressesLength(); ++i) {
            claimWiseFees(
                poolTokenAddresses[i]
            );
        }
    }

    /**
     * @dev Claim fees from wiseLending and send them to feeManager for
     * a specific pool.
     */
    function claimWiseFees(
        address _poolToken
    )
        public
    {
        address underlyingTokenAddress = _poolToken;

        uint256 shares = WISE_LENDING.getPositionLendingShares(
            FEE_MASTER_NFT_ID,
            _poolToken
        );

        if (shares == 0) {
            return;
        }

        uint256 tokenAmount = WISE_LENDING.withdrawExactShares(
            FEE_MASTER_NFT_ID,
            _poolToken,
            shares
        );

        if (isAaveToken[_poolToken] == true) {

            underlyingTokenAddress = underlyingToken[
                _poolToken
            ];

            AAVE.withdraw(
                underlyingTokenAddress,
                tokenAmount,
                address(this)
            );
        }

        if (totalBadDebtUSD == 0) {

            tokenAmount = _distributeIncentives(
                tokenAmount,
                _poolToken,
                underlyingTokenAddress
            );
        }

        _increaseFeeTokens(
            underlyingTokenAddress,
            tokenAmount
        );

        emit ClaimedFeesWise(
            underlyingTokenAddress,
            tokenAmount,
            block.timestamp
        );
    }

    /**
     * @dev Function for beneficial to claim gathered fees. Can only
     * claim fees for which the beneficial is allowed. Can only claim
     * token which are inside the feeManager.
     */
    function claimFeesBeneficial(
        address _feeToken,
        uint256 _amount
    )
        external
    {
        address caller = msg.sender;

        if (totalBadDebtUSD > 0) {
            revert ExistingBadDebt();
        }

        if (allowedTokens[caller][_feeToken] == false) {
            revert NotAllowed();
        }

        _decreaseFeeTokens(
            _feeToken,
            _amount
        );

        _safeTransfer(
            _feeToken,
            caller,
            _amount
        );

        emit ClaimedFeesBeneficial(
            caller,
            _feeToken,
            _amount,
            block.timestamp
        );
    }

    /**
     * @dev Function for paying back bad debt of a position. Caller
     * chooses postion, token and receive token. Only gathered fee token
     * can be distributed as receive token. Caller gets 5% more
     * in USDC value as incentive.
     */
    function payBackBadDebtForToken(
        uint256 _nftId,
        address _paybackToken,
        address _receivingToken,
        uint256 _shares
    )
        external
        returns (
            uint256 paybackAmount,
            uint256 receivingAmount
        )
    {
        updatePositionCurrentBadDebt(
            _nftId
        );

        if (badDebtPosition[_nftId] == 0) {
            return (
                0,
                0
            );
        }

        paybackAmount = WISE_LENDING.paybackAmount(
            _paybackToken,
            _shares
        );

        WISE_LENDING.corePaybackFeeManager(
            _paybackToken,
            _nftId,
            paybackAmount,
            _shares
        );

        _updateUserBadDebt(
            _nftId
        );

        receivingAmount = getReceivingToken(
            _paybackToken,
            _receivingToken,
            paybackAmount
        );

        _decreaseFeeTokens(
            _receivingToken,
            receivingAmount
        );

        _safeTransferFrom(
            _paybackToken,
            msg.sender,
            address(WISE_LENDING),
            paybackAmount
        );

        _safeTransfer(
            _receivingToken,
            msg.sender,
            receivingAmount
        );

        emit PayedBackBadDebt(
            _nftId,
            msg.sender,
            _paybackToken,
            _receivingToken,
            paybackAmount,
            block.timestamp
        );
    }

    /**
     * @dev Function for paying back bad debt of a position. Caller
     * chooses postion, token and receive token. Caller gets no
     * receive token!
     */
    function paybackBadDebtForFree(
        uint256 _nftId,
        address _paybackToken,
        uint256 _shares
    )
        external
        returns (uint256 paybackAmount)
    {
        updatePositionCurrentBadDebt(
            _nftId
        );

        if (badDebtPosition[_nftId] == 0) {
            return 0;
        }

        paybackAmount = WISE_LENDING.paybackAmount(
            _paybackToken,
            _shares
        );

        WISE_LENDING.corePaybackFeeManager(
            _paybackToken,
            _nftId,
            paybackAmount,
            _shares
        );

        _updateUserBadDebt(
            _nftId
        );

        _safeTransferFrom(
            _paybackToken,
            msg.sender,
            address(WISE_LENDING),
            paybackAmount
        );

        emit PayedBackBadDebtFree(
            _nftId,
            msg.sender,
            _paybackToken,
            paybackAmount,
            block.timestamp
        );
    }

    /**
     * @dev Returning the number of pool token
     * addresses saved inside the feeManager.
     */
    function getPoolTokenAddressesLength()
        public
        view
        returns (uint256)
    {
        return poolTokenAddresses.length;
    }

    /**
     * @dev Returns the pool token address
     * at the _index postion of the array.
     */
    function getPoolTokenAdressesByIndex(
        uint256 _index
    )
        external
        view
        returns (address)
    {
        return poolTokenAddresses[_index];
    }

    /**
     * @dev Bulk function for updating pools - loops through
     * all pools saved inside the poolTokenAddresses array.
     */
    function syncAllPools()
        external
    {
        for (uint8 i = 0; i < poolTokenAddresses.length; ++i) {
            WISE_LENDING.syncManually(
                poolTokenAddresses[i]
            );
        }
    }
}

// File: contracts/InterfaceHub/IAaveHub.sol



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

pragma solidity =0.8.21;

interface IAaveHub {

    function AAVE_ADDRESS()
        external
        view
        returns (address);
}
// 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/IWiseLending.sol



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

pragma solidity =0.8.21;

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

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

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

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

interface IWiseLending {

    function newBorrowRate(
        address _poolToken
    )
        external;

    function calculateBorrowShares(
        address _poolToken,
        uint256 _amount
    )
        external
        view
        returns (uint256);

    function borrowPoolData(
        address _poolToken
    )
        external
        view
        returns (BorrowPoolEntry memory);

    function lendingPoolData(
        address _poolToken
    )
        external
        view
        returns (LendingPoolEntry memory);

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

    function getTimeStamp(
        address _poolToken
    )
        external
        view
        returns (uint256);

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

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

    function veryfiedIsolationPool(
        address _poolAddress
    )
        external
        view
        returns (bool);

    function positionLocked(
        uint256 _nftId
    )
        external
        view
        returns (bool);

    function getTotalBareToken(
        address _poolToken
    )
        external
        view
        returns (uint256);

    function maxDepositValueToken(
        address _poolToken
    )
        external
        view
        returns (uint256);

    function master()
        external
        view
        returns (address);

    function WETH_ADDRESS()
        external
        view
        returns (address);

    function WISE_ORACLE()
        external
        view
        returns (address);

    function POSITION_NFT()
        external
        view
        returns (address);

    function FEE_MANAGER()
        external
        view
        returns (address);

    function WISE_SECURITY()
        external
        view
        returns (address);

    function lastUpdated(
        address _poolAddress
    )
        external
        view
        returns (uint256);

    function isolationPoolRegistered(
        uint256 _nftId,
        address _isolationPool
    )
        external
        view
        returns (bool);

    function calculateLendingShares(
        address _poolToken,
        uint256 _amount
    )
        external
        view
        returns (uint256);

    function positionPureCollateralAmount(
        uint256 _nftId,
        address _poolToken
    )
        external
        returns (uint256);

    function getTotalPool(
        address _poolToken
    )
        external
        view
        returns (uint256);

    function depositExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function withdrawOnBehalfExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function syncManually(
        address _poolToken
    )
        external;

    function withdrawOnBehalfExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        returns (uint256);

    function borrowOnBehalfExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function solelyDeposit(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external;

    function solelyWithdrawOnBehalf(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external;

    function paybackExactAmount(
        uint256 _nftId,
        address _poolToken,
        uint256 _amount
    )
        external
        returns (uint256);

    function paybackExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        returns (uint256);

    function setPoolFee(
        address _poolToken,
        uint256 _newFee
    )
        external;

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

    function withdrawExactShares(
        uint256 _nftId,
        address _poolToken,
        uint256 _shares
    )
        external
        returns (uint256);

    function poolTokenAddresses()
        external
        returns (address[] memory);

    function corePaybackFeeManager(
        address _poolToken,
        uint256 _nftId,
        uint256 _amount,
        uint256 _shares
    )
        external;

    /*
    function curveSecurityCheck(
        address _poolToken
    )
        external;
    */

    function preparePool(
        address _poolToken
    )
        external;

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

    function getPositionBorrowTokenByIndex(
        uint256 _nftId,
        uint256 _index
    )
        external
        view
        returns (address);

    function getPositionLendingTokenByIndex(
        uint256 _nftId,
        uint256 _index
    )
        external
        view
        returns (address);

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

    function globalPoolData(
        address _poolToken
    )
        external
        view
        returns (GlobalPoolEntry memory);


    function getGlobalBorrowAmount(
        address _token
    )
        external
        view
        returns (uint256);

    function getPseudoTotalBorrowAmount(
        address _token
    )
        external
        view
        returns (uint256);

    function getInitialBorrowAmountUser(
        address _user,
        address _token
    )
        external
        view
        returns (uint256);

    function getPseudoTotalPool(
        address _token
    )
        external
        view
        returns (uint256);

    function getInitialDepositAmountUser(
        address _user,
        address _token
    )
        external
        view
        returns (uint256);

    function getGlobalDepositAmount(
        address _token
    )
        external
        view
        returns (uint256);

    function paybackAmount(
        address _token,
        uint256 _shares
    )
        external
        view
        returns (uint256);

    function getPositionBorrowShares(
        address _user,
        address _token
    )
        external
        view
        returns (uint256);

    function getPositionLendingShares(
        address _user,
        address _token
    )
        external
        view
        returns (uint256);

    function cashoutAmount(
        address _token,
        uint256 _shares
    )
        external
        view
        returns (uint256);

    function getTotalDepositShares(
        address _token
    )
        external
        view
        returns (uint256);

    function getTotalBorrowShares(
        address _token
    )
        external
        view
        returns (uint256);

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

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

    function setRegistrationIsolationPool(
        uint256 _nftId,
        bool _state
    )
        external;
}

// 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/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 decimalsUSD()
        external
        pure
        returns (uint8);

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

    function setPreviousValue(
        address _tokenAddress
    )
        external;
}
// 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 reserved(
        address _owner
    )
        external
        view
        returns (uint256);

    function reservePosition()
        external;

    function mintPosition()
        external;

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

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

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

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



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

pragma solidity =0.8.21;

interface ICurve {

    function add_liquidity(
        address _pool,
        uint256[4] memory _depositAmounts,
        uint256 _minOutAmount
    )
        external
        returns (uint256);

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

    function get_dy(
        uint256 i,
        uint256 j,
        uint256 dx
    )
        external
        view
        returns (uint256);

    function get_dy_underlying(
        int128 i,
        int128 j,
        uint256 dx
    )
        external
        view
        returns (uint256);

    function exchange(
        int128 fromIndex,
        int128 toIndex,
        uint256 exactAmountFrom,
        uint256 minReceiveAmount
    )
        external;

    function exchange_underlying(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy
    )
        external;

    function remove_liquidity(
        address _pool,
        uint256 _burnAmount,
        uint256[4] memory coins
    )
        external;

    function remove_liquidity_one_coin(
        address _addy,
        uint256 _burnAmount,
        int128 _i,
        uint256 _minReceived
    )
        external;

    function coins(
        uint256 arg0
    )
        external
        view
        returns (address);

    function decimals()
        external
        view
        returns (uint8);

    function totalSupply()
        external
        view
        returns (uint256);

    function balances(
        uint256 arg0
    )
        external
        view
        returns (uint256);

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

// 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/FeeManager/FeeManagerHelper.sol



// SPDX-License-Identifier: -- WISE --
pragma solidity =0.8.21;

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

abstract contract FeeManagerHelper is DeclarationsFeeManager, TransferHelper {

    /**
     * @dev Internal update function which adds latest aquired token from borrow rate
     * for all borrow tokens of the position. Idnetical implementation like in wiseSecurity
     * or wiseLending.
     */
    function _prepareBorrows(
        uint256 _nftId
    )
        internal
    {
        uint256 i;

        for (i = 0; i < WISE_LENDING.getPositionBorrowTokenLength(_nftId); ++i) {

            address currentAddress = WISE_LENDING.getPositionBorrowTokenByIndex(
                _nftId,
                i
            );

            WISE_LENDING.preparePool(
                currentAddress
            );
        }
    }

    /**
     * @dev Internal update function which adds latest aquired token from borrow rate
     * for all collateral tokens of the position. Idnetical implementation like in wiseSecurity
     * or wiseLending.
     */
    function _prepareCollaterals(
        uint256 _nftId
    )
        internal
    {
        uint256 i;

        for (i = 0; i <  WISE_LENDING.getPositionLendingTokenLength(_nftId); ++i) {

            address currentAddress = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

            WISE_LENDING.preparePool(
                currentAddress
            );
        }
    }

    /**
     * @dev Internal set function for adjusting bad debt amount of a position.
     */
    function _setBadDebtPosition(
        uint256 _nftId,
        uint256 _amount
    )
        internal
    {
        badDebtPosition[_nftId] = _amount;
    }

    /**
     * @dev Internal increase function for global bad debt amount.
     */
    function _increaseTotalBadDebt(
        uint256 _amount
    )
        internal
    {
        totalBadDebtUSD += _amount;

        emit TotalBadDebtIncreased(
            _amount,
            block.timestamp
        );
    }

    /**
     * @dev Internal decrease function for global bad debt amount.
     */
    function _decreaseTotalBadDebt(
        uint256 _amount
    )
        internal
    {
        totalBadDebtUSD -= _amount;

        emit TotalBadDebtDecreased(
            _amount,
            block.timestamp
        );
    }

    /**
     * @dev Internal erease function to delete bad debt amount of a postion.
     */
    function _eraseBadDebtUser(
        uint256 _nftId
    )
        internal
    {
        delete badDebtPosition[_nftId];
    }

    /**
     * @dev Internal function updating bad debt amount of a position and global one (in USD).
     * Compares totalBorrow and totalCollateral of the postion in USD anadjustes bad debt
     * variables. Pseudo pool amounts needed to be updated before this function is called.
     */
    function _updateUserBadDebt(
        uint256 _nftId
    )
        internal
    {
        uint256 currentBorrowUSD = WISE_SECURITY.overallUSDBorrowHeartbeat(
            _nftId
        );

        uint256 currentCollateralBareUSD = WISE_SECURITY.overallUSDCollateralsBare(
            _nftId
        );

        uint256 currentBadDebt = badDebtPosition[
            _nftId
        ];

        if (currentBorrowUSD < currentCollateralBareUSD) {

            _eraseBadDebtUser(
                _nftId
            );

            _decreaseTotalBadDebt(
                currentBadDebt
            );

            emit UpdateBadDebtPosition(
                _nftId,
                0,
                block.timestamp
            );

            return;
        }

        uint256 newBadDebt = currentBorrowUSD
            - currentCollateralBareUSD;

        _setBadDebtPosition(
            _nftId,
            newBadDebt
        );

        newBadDebt > currentBadDebt
            ? _increaseTotalBadDebt(newBadDebt - currentBadDebt)
            : _decreaseTotalBadDebt(currentBadDebt - newBadDebt);

        emit UpdateBadDebtPosition(
            _nftId,
            newBadDebt,
            block.timestamp
        );
    }

    /**
     * @dev Internal increase function for tracking gathered fee token. No need for
     * balanceOf() checks.
     */
    function _increaseFeeTokens(
        address _feeToken,
        uint256 _amount
    )
        internal
    {
        feeTokens[_feeToken] += _amount;
    }

    /**
     * @dev Internal decrease function for tracking gathered fee token. No need for
     * balanceOf() checks.
     */
    function _decreaseFeeTokens(
        address _feeToken,
        uint256 _amount
    )
        internal
    {
        feeTokens[_feeToken] -= _amount;
    }

    /**
     * @dev Internal function to set benefical mapping for a certain token.
     */
    function _setAllowedTokens(
        address _user,
        address _feeToken,
        bool _state
    )
        internal
    {
        allowedTokens[_user][_feeToken] = _state;
    }

    /**
     * @dev Internal function calculating receive amount for the caller.
     * paybackIncentive is set to 5E16 => 5% incentive for paying back bad debt.
     */
    function getReceivingToken(
        address _paybackToken,
        address _receivingToken,
        uint256 _paybackAmount
    )
        public
        view
        returns (uint256 receivingAmount)
    {
        uint256 increasedAmount = _paybackAmount
            * (PRECISION_FACTOR_E18 + paybackIncentive)
            / PRECISION_FACTOR_E18;

        return ORACLE_HUB.getTokensFromUSD(
            _receivingToken,
            ORACLE_HUB.getTokensInUSD(
                _paybackToken,
                increasedAmount
            )
        );
    }

    /**
     * @dev Updates bad debt of a postion. Combines preparation of all
     * collaterals and borrows for passed _nftId with _updateUserBadDebt().
     */
    function updatePositionCurrentBadDebt(
        uint256 _nftId
    )
        public
    {
        _prepareCollaterals(
            _nftId
        );

        _prepareBorrows(
            _nftId
        );

        _updateUserBadDebt(
            _nftId
        );
    }

    /**
     * @dev Internal function for distributing incentives to incentiveOwnerA
     * and incentiveOwnerB.
     */
    function _distributeIncentives(
        uint256 _amount,
        address _poolToken,
        address _underlyingToken
    )
        internal
        returns (uint256)
    {
        uint256 reduceAmount;

        if (incentiveUSD[incentiveOwnerA] > 0) {

            reduceAmount += _gatherIncentives(
                _poolToken,
                _underlyingToken,
                incentiveOwnerA,
                _amount
            );
        }

        if (incentiveUSD[incentiveOwnerB] > 0) {

            reduceAmount += _gatherIncentives(
                _poolToken,
                _underlyingToken,
                incentiveOwnerB,
                _amount
            );
        }

        return _amount - reduceAmount;
    }

    /**
     * @dev Internal function computing the incentive amount for an incentiveOwner
     * depending of the amount per fee token. Reduces the open incentive amount for
     * the owner.
     */
    function _gatherIncentives(
        address _poolToken,
        address _underlyingToken,
        address _incentiveOwner,
        uint256 _amount
    )
        internal
        returns (uint256 )
    {
        uint256 incentiveAmount = _amount
            * INCENTIVE_PORTION
            / WISE_LENDING.globalPoolData(_poolToken).poolFee;

        uint256 usdEquivalent = ORACLE_HUB.getTokensInUSD(
            _poolToken,
            incentiveAmount
        );

        uint256 reduceUSD = usdEquivalent < incentiveUSD[_incentiveOwner]
            ? usdEquivalent
            : incentiveUSD[_incentiveOwner];

        if (reduceUSD == usdEquivalent) {

            incentiveUSD[_incentiveOwner] -= usdEquivalent;

            gatheredIncentiveToken
                [_incentiveOwner]
                [_underlyingToken] += incentiveAmount;

            return incentiveAmount;
        }

        incentiveAmount = ORACLE_HUB.getTokensFromUSD(
            _poolToken,
            reduceUSD
        );

        delete incentiveUSD[
            _incentiveOwner
        ];

        gatheredIncentiveToken[_incentiveOwner][_underlyingToken] += incentiveAmount;

        return incentiveAmount;
    }

    /**
     * @dev Internal function checking if the
     * passed value is smaller 100% and bigger 1%.
     */
    function _checkValue(
        uint256 _value
    )
        internal
        pure
    {
        if (_value < PRECISION_FACTOR_E16) {
            revert TooLowValue();
        }

        if (_value > PRECISION_FACTOR_E18) {
            revert TooHighValue();
        }
    }
}
// File: contracts/TransferHub/TransferHelper.sol



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

pragma solidity =0.8.21;

import "./CallOptionalReturn.sol";

contract TransferHelper is CallOptionalReturn {

    bytes4 constant transfer = IERC20
        .transfer
        .selector;

    bytes4 constant transferFrom = IERC20
        .transferFrom
        .selector;

    /**
     * @dev
     * Allows to execute safe transfer for a token
     */
    function _safeTransfer(
        address _token,
        address _to,
        uint256 _value
    )
        internal
    {
        _callOptionalReturn(
            _token,
            abi.encodeWithSelector(
                transfer,
                _to,
                _value
            )
        );
    }

    /**
     * @dev
     * Allows to execute safe transferFrom for a token
     */
    function _safeTransferFrom(
        address _token,
        address _from,
        address _to,
        uint256 _value
    )
        internal
    {
        _callOptionalReturn(
            _token,
            abi.encodeWithSelector(
                transferFrom,
                _from,
                _to,
                _value
            )
        );
    }
}

// File: contracts/FeeManager/DeclarationsFeeManager.sol



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

pragma solidity =0.8.21;

import "../InterfaceHub/IERC20.sol";
import "../InterfaceHub/IWiseLending.sol";
import "../InterfaceHub/IFeeManager.sol";
import "../InterfaceHub/IWiseSecurity.sol";
import "../InterfaceHub/IPositionNFTs.sol";
import "../InterfaceHub/IWiseOracleHub.sol";
import "../WrapperHub/InterfacesWrapperHub.sol";

import "../OwnableMaster.sol";
import "./FeeManagerEvents.sol";

error NotWiseLiquidation();
error AlreadySet();
error ExistingBadDebt();
error TransferFromFailedFeeManager();
error TransferFailedFeeManager();
error NotWiseLending();
error NotIncentiveMaster();
error PoolAlreadyAdded();
error TooHighValue();
error TooLowValue();
error NotAllowed();


contract DeclarationsFeeManager is FeeManagerEvents, OwnableMaster {

    constructor(
        address _master,
        address _aaveAddress,
        address _wiseLendingAddress,
        address _oracleHubAddress,
        address _wiseSecurityAddress,
        address _positionNFTAddress
    )
        OwnableMaster(
            _master
        )
    {
        WISE_LENDING = IWiseLending(
            _wiseLendingAddress
        );

        AAVE = IAave(
            _aaveAddress
        );

        ORACLE_HUB = IWiseOracleHub(
            _oracleHubAddress
        );

        WISE_SECURITY = IWiseSecurity(
            address(_wiseSecurityAddress)
        );

        POSITION_NFTS = IPositionNFTs(
            address(_positionNFTAddress)
        );

        POSITION_NFTS.mintPosition();

        incentiveMaster = _master;

        paybackIncentive = 5 * PRECISION_FACTOR_E16;

        incentiveOwnerA = 0xf69A0e276664997357BF987df83f32a1a3F80944;
        incentiveOwnerB = 0x8f741ea9C9ba34B5B8Afc08891bDf53faf4B3FE7;

        incentiveUSD[incentiveOwnerA] = 196000 * PRECISION_FACTOR_E18;
        incentiveUSD[incentiveOwnerB] = 213000 * PRECISION_FACTOR_E18;
    }

    // ---- Interfaces ----

    // Interface aave V3 contract
    IAave public immutable AAVE;

    // Interface wiseLending contract
    IWiseLending public immutable WISE_LENDING;

    // Interface position NFT contract
    IPositionNFTs public immutable POSITION_NFTS;

    // Interface wiseSecurity contract
    IWiseSecurity public immutable WISE_SECURITY;

    // Interface wise oracleHub contract
    IWiseOracleHub public immutable ORACLE_HUB;


    // ---- Variables ----

    // Global total bad debt variable
    uint256 public totalBadDebtUSD;

    // Incentive percentage for paying back bad debt
    uint256 public paybackIncentive;

    // Array of pool tokens in wiseLending
    address[] public poolTokenAddresses;

    // Address of incentive master
    address public incentiveMaster;

    // Proposed incentive master (for changing)
    address public proposedIncentiveMaster;

    // Address of incentive owner A
    address public incentiveOwnerA;

    // Address of incentive owner B
    address public incentiveOwnerB;


    // ---- Mappings ----

    // Bad debt of a specific position
    mapping (uint256 => uint256) public badDebtPosition;

    // Amount of fee token inside feeManager
    mapping (address => uint256) public feeTokens;

    // Open incetive amount for incentiveOwner in USD
    mapping (address => uint256) public incentiveUSD;

    // Flag that specific token is already added
    mapping (address => bool) public poolTokenAdded;

    // Flag for token being aToken
    mapping (address => bool) public isAaveToken;

    // Getting underlying token of aave aToken
    mapping (address => address) public underlyingToken;

    // Showing which token are allowed to claim for beneficial address
    mapping (address => mapping (address => bool)) public allowedTokens;

    // Gives claimable token amount for incentiveOwner per token
    mapping (address => mapping (address => uint256)) public gatheredIncentiveToken;

    // Position NFT id of the feeManager
    uint256 public constant FEE_MASTER_NFT_ID = 0;

    // Precision factors for computations
    uint256 constant PRECISION_FACTOR_E15 = 1E15;
    uint256 constant PRECISION_FACTOR_E16 = 1E16;
    uint256 constant PRECISION_FACTOR_E18 = 1E18;

    // Maximal amount for 256 byte number
    uint256 constant HUGE_AMOUNT = type(uint256).max;

    // Base portion from gathered fees for incentiveOwners (0.5%)
    uint256 constant public INCENTIVE_PORTION = 5 * PRECISION_FACTOR_E15;


    // ---- Modifier ----

    modifier onlyWiseSecurity() {
        _onlyWiseSecurity();
        _;
    }

    modifier onlyWiseLending() {
        _onlyWiseLending();
        _;
    }

    modifier onlyIncentiveMaster() {
        _onlyIncentiveMaster();
        _;
    }

    function _onlyIncentiveMaster()
        private
        view
    {
        if (msg.sender == incentiveMaster) {
            return;
        }

        revert NotIncentiveMaster();
    }

    function _onlyWiseSecurity()
        private
        view
    {
        if (msg.sender == address(WISE_SECURITY)) {
            return;
        }

        revert NotWiseLiquidation();
    }

    function _onlyWiseLending()
        private
        view
    {
        if (msg.sender == address(WISE_LENDING)) {
            return;
        }

        revert NotWiseLending();
    }
}
// File: contracts/FeeManager/FeeManagerEvents.sol



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

pragma solidity =0.8.21;

contract FeeManagerEvents {

    event PoolTokenAdded(
        address poolToken,
        uint256 timestamp
    );

    event BadDebtIncreasedLiquidation(
        uint256 amount,
        uint256 timestamp
    );

    event TotalBadDebtIncreased(
        uint256 amount,
        uint256 timestamp
    );

    event TotalBadDebtDecreased(
        uint256 amount,
        uint256 timestamp
    );

    event SetBadDebtPosition(
        uint256 nftId,
        uint256 amount,
        uint256 timestamp
    );

    event UpdateBadDebtPosition(
        uint256 nftId,
        uint256 newAmount,
        uint256 timestamp
    );

    event SetBeneficial(
        address user,
        address[] token,
        uint256 timestamp
    );

    event RevokeBeneficial(
        address user,
        address[] token,
        uint256 timestamp
    );

    event ClaimedFeesWise(
        address token,
        uint256 amount,
        uint256 timestamp
    );

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

    event PayedBackBadDebt(
        uint256 nftId,
        address indexed sender,
        address paybackToken,
        address receivingToken,
        uint256 indexed paybackAmount,
        uint256 timestamp
    );

    event PayedBackBadDebtFree(
        uint256 nftId,
        address indexed sender,
        address paybackToken,
        uint256 indexed paybackAmount,
        uint256 timestampp
    );
}
// File: contracts/WrapperHub/InterfacesWrapperHub.sol



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

pragma solidity =0.8.21;

import '../InterfaceHub/IERC20.sol';

interface IAave is IERC20 {

    struct ReserveData {

        // Stores the reserve configuration
        ReserveConfigurationMap configuration;

        // Liquidity index. Expressed in ray
        uint128 liquidityIndex;

        // Current supply rate. Expressed in ray
        uint128 currentLiquidityRate;

        // Variable borrow index. Expressed in ray
        uint128 variableBorrowIndex;

        // Current variable borrow rate. Expressed in ray
        uint128 currentVariableBorrowRate;

        // Current stable borrow rate. Expressed in ray
        uint128 currentStableBorrowRate;

        // Timestamp of last update
        uint40 lastUpdateTimestamp;

        // Id of the reserve.
        uint16 id;

        // aToken address
        address aTokenAddress;

        // stableDebtToken address
        address stableDebtTokenAddress;

        // VariableDebtToken address
        address variableDebtTokenAddress;

        // Address of the interest rate strategy
        address interestRateStrategyAddress;

        // Current treasury balance, scaled
        uint128 accruedToTreasury;

        // Outstanding unbacked aTokens minted through the bridging feature
        uint128 unbacked;

        // Outstanding debt borrowed against this asset in isolation mode
        uint128 isolationModeTotalDebt;
    }

    struct ReserveConfigurationMap {
        uint256 data;
    }

    function deposit(
        address _token,
        uint256 _amount,
        address _owner,
        uint16 _referralCode
    )
        external;

    function withdraw(
        address _token,
        uint256 _amount,
        address _recipient
    )
        external
        returns (uint256);

    function getReserveData(
        address asset
    )
        external
        view
        returns (ReserveData memory);
}
// File: contracts/InterfaceHub/IWiseSecurity.sol



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

pragma solidity =0.8.21;

struct CurveSwapStructToken {
    uint256 curvePoolTokenIndexFrom;
    uint256 curvePoolTokenIndexTo;
    uint256 curveMetaPoolTokenIndexFrom;
    uint256 curveMetaPoolTokenIndexTo;
}

struct CurveSwapStructData {
    address curvePool;
    address curveMetaPool;
    bytes swapBytesPool;
    bytes swapBytesMeta;
}

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 _tokenToPayback,
        uint256 _shareAmountToPay
    )
        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 curveSecurityCheck(
        address _poolAddress
    )
        external;

    function prepareCurvePools(
        address _poolToken,
        CurveSwapStructData memory _curveSwapStructData,
        CurveSwapStructToken memory _curveSwapStructToken
    )
        external;

    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 calculateWishPercentage(
        uint256 _nftId,
        address _receiveToken,
        uint256 _paybackUSD,
        uint256 _maxFeeUSD,
        uint256 _baseRewardLiquidation
    )
        external
        view
        returns (uint256);

    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 maxFeeUSD()
        external
        view
        returns (uint256);

    function maxFeeFarmUSD()
        external
        view
        returns (uint256);

    function baseRewardLiquidation()
        external
        view
        returns (uint256);

    function baseRewardLiquidationFarm()
        external
        view
        returns (uint256);

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

    function getLendingRate(
        address _poolToken
    )
        external
        view
        returns (uint256);
}