// File: @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

// File: @openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// File: @openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol



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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

// File: @openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol



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

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// File: @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol



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

pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";

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

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

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

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

// File: contracts/factory/KyokoFactory.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.18;

import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "../interfaces/IKyokoFactory.sol";
import "../interfaces/IKyokoPool.sol";
import "../interfaces/IKyokoPoolAddressesProvider.sol";
import "../interfaces/IKyokoPoolConfigurator.sol";
import "../interfaces/IInterestRateStrategy.sol";
import "../interfaces/IKToken.sol";
import "../interfaces/IStableDebtToken.sol";
import "../interfaces/IVariableDebtToken.sol";
import "../libraries/utils/Errors.sol";
import "../tokens/VariableDebtToken.sol";

contract KyokoFactory is IKyokoFactory, ContextUpgradeable {
    address internal WETH;
    IKyokoPoolAddressesProvider public _addressesProvider;
    address public _treasury;
    uint16 public _factor;
    uint40 internal MIN_BORROW_TIME;
    uint256 public _initilLiquidity = 0.01 ether;
    uint16 public _liquidationThreshold = 13333;
    uint32 public _lockTime = 30 days;
    bool only = true;

    address internal _createKToken;
    address internal _createDebtToken;

    string private s1 = " ";
    string private s2 = "ETH";

    mapping(address => address) public kTokenCreator;
    mapping(address => address) public sTokenCreator;
    mapping(address => address) public vTokenCreator;

    modifier onlyPoolAdmin() {
        require(
            _addressesProvider.isAdmin(_msgSender()),
            Errors.CALLER_NOT_POOL_ADMIN
        );
        _;
    }

    modifier onlyPoolAdminCreater() {
        if (only) {
            require(
                _addressesProvider.isAdmin(_msgSender()),
                Errors.CALLER_NOT_POOL_ADMIN
            );
        }
        _;
    }

    modifier onlyKyokoPoolConfigurator() {
        _onlyKyokoPoolConfigurator();
        _;
    }

    function _onlyKyokoPoolConfigurator() internal view {
        require(
            _addressesProvider.isConfigurator(_msgSender()),
            Errors.LP_CALLER_NOT_KYOKO_POOL_CONFIGURATOR
        );
    }

    constructor(
        address weth,
        address provider,
        address treasury,
        address createKToken,
        address createDebtToken
    ) initializer {
        WETH = weth;
        _addressesProvider = IKyokoPoolAddressesProvider(provider);
        _treasury = treasury;
        _factor = 2000;
        MIN_BORROW_TIME = 5 minutes;
        _createKToken = createKToken;
        _createDebtToken = createDebtToken;
    }

    function createPool(address _nftAddress)
        external
        override
        onlyPoolAdminCreater
        returns (
            address kTokenAddress,
            address variableDebtAddress,
            address stableDebtAddress
        )
    {
        string memory _nftSymbol = INFT(_nftAddress).symbol();
        IKyokoPool pool = _getKyokoPool();
        uint256 reservesCount = pool.getReservesCount();

        kTokenAddress = createK(reservesCount, _nftSymbol);
        variableDebtAddress = createVariableDebtToken(
            WETH,
            address(_addressesProvider),
            reservesCount,
            _nftSymbol
        );
        stableDebtAddress = createStable(reservesCount, _nftSymbol);
        uint256 blockNumber = block.number;

        kTokenCreator[kTokenAddress] = msg.sender;
        sTokenCreator[stableDebtAddress] = msg.sender;
        vTokenCreator[variableDebtAddress] = msg.sender;

        emit CreatePool(
            blockNumber,
            kTokenAddress,
            variableDebtAddress,
            stableDebtAddress
        );
    }

    function createSharedPool()
        external
        override
        onlyPoolAdmin
        returns (
            address kTokenAddress,
            address variableDebtAddress,
            address stableDebtAddress
        )
    {
        string memory _nftSymbol = "SHARED";
        IKyokoPool pool = _getKyokoPool();
        uint256 reservesCount = pool.getReservesCount();

        kTokenAddress = createK(reservesCount, _nftSymbol);
        variableDebtAddress = createVariableDebtToken(
            WETH,
            address(_addressesProvider),
            reservesCount,
            _nftSymbol
        );
        stableDebtAddress = createStable(reservesCount, _nftSymbol);
        uint256 blockNumber = block.number;

        kTokenCreator[kTokenAddress] = msg.sender;
        sTokenCreator[stableDebtAddress] = msg.sender;
        vTokenCreator[variableDebtAddress] = msg.sender;

        emit CreatePool(
            blockNumber,
            kTokenAddress,
            variableDebtAddress,
            stableDebtAddress
        );
    }

    function initReserve(
        address _nftAddress,
        uint40 _period,
        uint16 _ratio,
        uint24 _liqDuration,
        uint24 _bidDuration,
        bool _enabledStableBorrow,
        address kTokenAddress,
        address variableDebtAddress,
        address stableDebtAddress,
        DataTypes.RateStrategyInput memory _rateInput
    ) external payable override onlyPoolAdminCreater {
        uint256 amount = msg.value;
        require(amount >= _initilLiquidity, Errors.KF_LIQUIDITY_INSUFFICIENT);
        require(
            kTokenCreator[kTokenAddress] == msg.sender &&
                sTokenCreator[stableDebtAddress] == msg.sender &&
                vTokenCreator[variableDebtAddress] == msg.sender,
            Errors.KT_ERROR_CREATOR
        );
        DataTypes.InitReserveInput memory input;
        DataTypes.RateStrategyInput memory rateInput = _rateInput;
        input.underlyingAsset = _nftAddress;
        input.treasury = _treasury;
        input.factor = _factor;
        input.borrowRatio = _ratio;
        input.period = _period;
        input.minBorrowTime = MIN_BORROW_TIME;
        input.liqThreshold = _liquidationThreshold;
        input.liqDuration = _liqDuration;
        input.bidDuration = _bidDuration;
        input.lockTime = _lockTime;
        input.stableBorrowed = _enabledStableBorrow;
        IKyokoPool pool = _getKyokoPool();
        IKyokoPoolConfigurator configurator = IKyokoPoolConfigurator(
            _addressesProvider.getKyokoPoolConfigurator()[0]
        );
        uint256 reservesCount = rateInput.reserveId = input.reserveId = pool
            .getReservesCount();

        input.kTokenImpl = kTokenAddress;
        input.variableDebtTokenImpl = variableDebtAddress;
        input.stableDebtTokenImpl = stableDebtAddress;
        input.interestRateStrategyAddress = _addressesProvider
            .getRateStrategy()[0];

        configurator.factoryInitReserve(input, rateInput);

        pool.deposit{value: amount}(reservesCount, msg.sender);
    }

    function setFactor(uint16 factor)
        external
        override
        onlyKyokoPoolConfigurator
    {
        _factor = factor;
        emit FactorUpdate(factor);
    }

    function setInitialLiquidity(uint256 amount)
        external
        override
        onlyKyokoPoolConfigurator
    {
        _initilLiquidity = amount;
        emit InitilLiquidityUpdate(amount);
    }

    function setLiqThreshold(uint16 threshold)
        external
        override
        onlyKyokoPoolConfigurator
    {
        _liquidationThreshold = threshold;
        emit LiquidationThreshold(threshold);
    }

    function setLockTime(uint32 lockTime)
        external
        override
        onlyKyokoPoolConfigurator
    {
        _lockTime = lockTime;
        emit LockTime(lockTime);
    }

    function setTokenFactory(address createKToken, address createDebtToken)
        external
        override
        onlyKyokoPoolConfigurator
    {
        if (createKToken != address(0)) {
            _createKToken = createKToken;
        }
        if (createDebtToken != address(0)) {
            _createDebtToken = createDebtToken;
        }
        emit FactoryUpdate(createKToken, createDebtToken);
    }

    function switchOnly() external override onlyPoolAdmin {
        if (only) {
            only = false;
        } else {
            only = true;
        }
    }

    function createK(uint256 reserveId, string memory _nftSymbol)
        internal
        returns (address kTokenAddress)
    {
        (bool success, bytes memory result) = _createKToken.delegatecall(
            abi.encodeWithSignature(
                "createKToken(address,address,address,uint256,string,string,string)",
                WETH,
                address(_addressesProvider),
                _treasury,
                reserveId,
                _nftSymbol,
                s1,
                s2
            )
        );
        require(success, Errors.KT_CREATION_FAILED);

        kTokenAddress = abi.decode(result, (address));
    }

    function createVariableDebtToken(
        address _weth,
        address _provider,
        uint256 _reserveId,
        string memory symbol
    ) internal returns (address variableAddress) {
        address weth = _weth;
        IKyokoPoolAddressesProvider provider = IKyokoPoolAddressesProvider(
            _provider
        );
        uint256 reserveId = _reserveId;
        string memory s3 = "Kyoko variable bearing ";
        string memory s4 = "kVariable";
        string memory hVariableName = string(
            abi.encodePacked(s3, symbol, s1, s2)
        );
        string memory hVariableSymbol = string(
            abi.encodePacked(s4, symbol, s2)
        );
        VariableDebtToken variableDebtToken = new VariableDebtToken(
            provider,
            reserveId,
            weth,
            18,
            hVariableName,
            hVariableSymbol
        );
        variableAddress = address(variableDebtToken);

        emit CreateVariableToken(msg.sender, variableAddress);
    }

    function createStable(uint256 reserveId, string memory _nftSymbol)
        internal
        returns (address stableDebtAddress)
    {
        (bool success, bytes memory result) = _createDebtToken.delegatecall(
            abi.encodeWithSignature(
                "createStableDebtToken(address,address,uint256,string,string,string)",
                WETH,
                address(_addressesProvider),
                reserveId,
                _nftSymbol,
                s1,
                s2
            )
        );
        require(success, Errors.SDT_CREATION_FAILED);

        stableDebtAddress = abi.decode(result, (address));
    }

    function _getKyokoPool() internal view returns (IKyokoPool) {
        return IKyokoPool(_addressesProvider.getKyokoPool()[0]);
    }
}

// File: contracts/interfaces/ICreditDelegationToken.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

interface ICreditDelegationToken {
  event BorrowAllowanceDelegated(
    address indexed fromUser,
    address indexed toUser,
    address asset,
    uint256 amount
  );

  /**
   * @dev delegates borrowing power to a user on the specific debt token
   * @param delegatee the address receiving the delegated borrowing power
   * @param amount the maximum amount being delegated. Delegation will still
   * respect the liquidation constraints (even if delegated, a delegatee cannot
   * force a delegator HF to go below 1)
   **/
  function approveDelegation(address delegatee, uint256 amount) external;

  /**
   * @dev returns the borrow allowance of the user
   * @param fromUser The user to giving allowance
   * @param toUser The user to give allowance to
   * @return the current allowance of toUser
   **/
  function borrowAllowance(address fromUser, address toUser) external view returns (uint256);
}

// File: contracts/interfaces/IInitializableDebtToken.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "./IKyokoPoolAddressesProvider.sol";

/**
 * @title IInitializableDebtToken
 * @notice Interface for the initialize function common between debt tokens
 * @author Kyoko
 **/
interface IInitializableDebtToken {
  /**
   * @dev Emitted when a debt token is initialized
   * @param underlyingAsset The address of the underlying asset
   * @param pool The address of the associated lending pool
   * @param reserveId The id of the reserve
   * @param debtTokenDecimals the decimals of the debt token
   * @param debtTokenName the name of the debt token
   * @param debtTokenSymbol the symbol of the debt token
   **/
  event Initialized(
    address indexed underlyingAsset,
    address indexed pool,
    uint256 reserveId,
    uint8 debtTokenDecimals,
    string debtTokenName,
    string debtTokenSymbol
  );

  /**
   * @dev Initializes the debt token.
   * @param provider The address of the address provider where this debtToken will be used
   * @param reserveId The id of the reserve
   * @param underlyingAsset The address of the underlying asset of this kToken (E.g. WETH for hWETH)
   * @param debtTokenDecimals The decimals of the debtToken, same as the underlying asset's
   * @param debtTokenName The name of the token
   * @param debtTokenSymbol The symbol of the token
   */
  function initialize(
    IKyokoPoolAddressesProvider provider,
    uint256 reserveId,
    address underlyingAsset,
    uint8 debtTokenDecimals,
    string memory debtTokenName,
    string memory debtTokenSymbol
  ) external;
}

// File: contracts/interfaces/IInterestRateStrategy.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "../libraries/utils/DataTypes.sol";

interface IInterestRateStrategy {
    event SetMinBorrowTime(uint40 newTime);

    event VariableRateUpdated(
        uint256 indexed reserveId,
        uint256 newBaseStableRate,
        uint256 newVariableSlope1,
        uint256 newVariableSlope2
    );

    event StableRateUpdated(
        uint256 indexed reserveId,
        uint256 newBaseStableRate,
        uint256 newVariableSlope1,
        uint256 newVariableSlope2
    );

    event AddNewRate(uint256 reserveId);

    function getRate(uint256 reserveId)
        external
        view
        returns (DataTypes.Rate memory);

    function getMaxVariableBorrowRate(uint256 reserveId)
        external
        view
        returns (uint256);

    function calculateInterestRates(
        uint256 reserveId,
        address reserve,
        address kToken,
        uint256 liquidityAdded,
        uint256 liquidityTaken,
        uint256 totalStableDebt,
        uint256 totalVariableDebt,
        uint256 averageStableBorrowRate,
        uint256 reserveFactor
    )
        external
        view
        returns (
            uint256,
            uint256,
            uint256
        );

    function calculateInterestRates(
        DataTypes.Rate memory rate,
        uint256 availableLiquidity,
        uint256 totalStableDebt,
        uint256 totalVariableDebt,
        uint256 averageStableBorrowRate,
        uint256 reserveFactor
    )
        external
        view
        returns (
            uint256,
            uint256,
            uint256
        );
    
    function setRate(
        uint256 reserveId,
        uint256 _optimalUtilizationRate,
        uint256 _baseVariableBorrowRate,
        uint256 _variableSlope1,
        uint256 _variableSlope2,
        uint256 _baseStableBorrowRate,
        uint256 _stableSlope1,
        uint256 _stableSlope2
    ) external;

    function setVariableRate(
        uint256 reserveId,
        uint256 _baseVariableRate,
        uint256 _variableSlope1,
        uint256 _variableSlope2
    ) external;

    function setStableRate(
        uint256 reserveId,
        uint256 _baseStableRate,
        uint256 _stableSlope1,
        uint256 _stableSlope2
    ) external;
}

// File: contracts/interfaces/IKToken.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "./IScaledBalanceToken.sol";
import "./IKyokoPoolAddressesProvider.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

interface IKToken is IERC20Upgradeable, IScaledBalanceToken {
    /**
     * @dev Emitted when an kToken is initialized
     * @param underlyingAsset The address of the underlying asset
     * @param pool The address of the associated lending pool
     * @param reserveId The id of the reserves
     * @param treasury The address of the treasury
     * @param kTokenDecimals the decimals of the underlying
     * @param kTokenName the name of the kToken
     * @param kTokenSymbol the symbol of the kToken
     **/
    event Initialize(
        address indexed underlyingAsset,
        address indexed pool,
        uint256 indexed reserveId,
        address treasury,
        uint8 kTokenDecimals,
        string kTokenName,
        string kTokenSymbol
    );

    /**
     * @dev Initializes the kToken
     * @param pool The address of the lending pool where this kToken will be used
     * @param reserveId The id of the reserves
     * @param treasury The address of the Kyoko treasury, receiving the fees on this kToken
     * @param underlyingAsset The address of the underlying asset of this kToken (E.g. WETH for kWETH)
     * @param kTokenDecimals The decimals of the kToken, same as the underlying asset's
     * @param kTokenName The name of the kToken
     * @param kTokenSymbol The symbol of the kToken
     */
    function initialize(
        IKyokoPoolAddressesProvider pool,
        uint256 reserveId,
        address treasury,
        address underlyingAsset,
        uint8 kTokenDecimals,
        string calldata kTokenName,
        string calldata kTokenSymbol
    ) external;

    /**
     * @dev Emitted after the mint action
     * @param from The address performing the mint
     * @param value The amount being
     * @param index The new liquidity index of the reserve
     **/
    event Mint(address indexed from, uint256 value, uint256 index);

    /**
     * @dev Mints `amount` kTokens to `user`
     * @param user The address receiving the minted tokens
     * @param amount The amount of tokens getting minted
     * @param index The new liquidity index of the reserve
     * @return `true` if the the previous balance of the user was 0
     */
    function mint(
        address user,
        uint256 amount,
        uint256 index
    ) external returns (bool);

    /**
     * @dev Emitted after kTokens are burned
     * @param from The owner of the kTokens, getting them burned
     * @param target The address that will receive the underlying
     * @param value The amount being burned
     * @param index The new liquidity index of the reserve
     **/
    event Burn(
        address indexed from,
        address indexed target,
        uint256 value,
        uint256 index
    );

    /**
     * @dev Emitted during the transfer action
     * @param from The user whose tokens are being transferred
     * @param to The recipient
     * @param value The amount being transferred
     * @param index The new liquidity index of the reserve
     **/
    event BalanceTransfer(
        address indexed from,
        address indexed to,
        uint256 value,
        uint256 index
    );

    /**
     * @dev Burns kTokens from `user` and sends the equivalent amount of underlying to `receiverOfUnderlying`
     * @param user The owner of the kTokens, getting them burned
     * @param receiverOfUnderlying The address that will receive the underlying
     * @param amount The amount being burned
     * @param index The new liquidity index of the reserve
     **/
    function burn(
        address user,
        address receiverOfUnderlying,
        uint256 amount,
        uint256 index
    ) external;

    /**
     * @dev Burns kTokens from `user`
     * @param user The owner of the kTokens, getting them burned
     * @param amount The amount being burned
     * @param index The new liquidity index of the reserve
     **/
    function burn(address user, uint256 amount, uint256 index) external;

    /**
     * @dev Mints kTokens to the reserve treasury
     * @param amount The amount of tokens getting minted
     * @param index The new liquidity index of the reserve
     */
    function mintToTreasury(uint256 amount, uint256 index) external;

    /**
     * @dev Transfers kTokens in the event of a borrow being liquidated, in case the liquidators reclaims the kToken
     * @param from The address getting liquidated, current owner of the kTokens
     * @param to The recipient
     * @param value The amount of tokens getting transferred
     **/
    function transferOnLiquidation(
        address from,
        address to,
        uint256 value
    ) external;

    /**
     * @dev Transfers the underlying asset to `target`. Used by the KyokoPool to transfer
     * assets in borrow(), withdraw() and flashLoan()
     * @param user The recipient of the underlying
     * @param amount The amount getting transferred
     * @return The amount transferred
     **/
    function transferUnderlyingTo(
        address user,
        uint256 amount
    ) external returns (uint256);

    function transferUnderlyingNFTTo(
        address nft,
        address target,
        uint256 nftId
    ) external returns (uint256);

    /**
     * @dev Invoked to execute actions on the kToken side after a repayment.
     * @param user The user executing the repayment
     * @param amount The amount getting repaid
     **/
    function handleRepayment(address user, uint256 amount) external;

    /**
     * @dev Returns the address of the underlying asset of this kToken (E.g. WETH for aWETH)
     **/
    function UNDERLYING_ASSET_ADDRESS() external view returns (address);
}

// File: contracts/interfaces/IKyokoFactory.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "../libraries/utils/DataTypes.sol";

interface IKyokoFactory {
    event CreatePool(uint256 indexed blockNumber, address kToken, address variableDebtAddress, address stableDebtAddress);
    event CreateKToken(address indexed user, address kToken);
    event CreateVariableToken(address indexed user, address variableDebtAddress);
    event CreateStableToken(address indexed user, address stableDebtAddress);
    event FactorUpdate(uint16 factor);
    event InitilLiquidityUpdate(uint256 amount);
    event LiquidationThreshold(uint16 threshold);
    event LockTime(uint32 lockTime);
    event FactoryUpdate(address kToken, address debtToken);

    function createPool(
        address _nftAddress
    ) external returns (address kTokenAddress, address variableDebtAddress, address stableDebtAddress);

    function createSharedPool() external returns (address kTokenAddress, address variableDebtAddress, address stableDebtAddress);

    function initReserve(
        address _nftAddress,
        uint40 _period,
        uint16 _ratio,
        uint24 _liqDuration,
        uint24 _bidDuration,
        bool _enabledStableBorrow,
        address kTokenAddress, 
        address variableDebtAddress, 
        address stableDebtAddress,
        DataTypes.RateStrategyInput memory _rateInput
    ) external payable;

    function setFactor(uint16 _factor) external;
    function setInitialLiquidity(uint256 amount) external;
    function setLiqThreshold(uint16 threshold) external;
    function setLockTime(uint32 lockTime) external;
    function setTokenFactory(address _createKToken, address _createDebtToken) external;
    function switchOnly() external;
}

interface INFT {
    function symbol() external view returns (string memory);
}
// File: contracts/interfaces/IKyokoPool.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "../libraries/utils/DataTypes.sol";

interface IKyokoPool {
    /**
     * @dev Emitted on deposit()
     * @param reserveId The id of the reserve
     * @param user The beneficiary of the deposit, receiving the kTokens
     * @param onBehalfOf The beneficiary of the deposit, receiving the kTokens
     * @param amount The amount deposited
     **/
    event Deposit(
        uint256 indexed reserveId,
        address indexed user,
        address indexed onBehalfOf,
        uint256 amount
    );

    /**
     * @dev Emitted on withdraw()
     * @param reserveId The id of the reserve
     * @param user The address initiating the withdrawal, owner of kTokens
     * @param to Address that will receive the underlying
     * @param amount The amount to be withdrawn
     **/
    event Withdraw(
        uint256 indexed reserveId,
        address indexed user,
        address indexed to,
        uint256 amount
    );

    /**
     * @dev Emitted on borrow() and flashLoan() when debt needs to be opened
     * @param reserveId The id of the reserve
     * @param borrowId The id of the borrow info
     * @param asset The address of the borrowed nft
     * @param nftId The tokenId of the borrowed nft
     * @param borrowMode The rate mode: 1 for Stable, 2 for Variable
     * @param amount The amount of the borrow
     * @param borrowRate The numeric rate at which the user has borrowed
     **/
    event Borrow(
        uint256 indexed reserveId,
        uint256 indexed borrowId,
        address indexed asset,
        uint256 nftId,
        uint256 borrowMode,
        uint256 amount,
        uint256 borrowRate
    );

    /**
     * @dev Emitted on repay()
     * @param reserveId The id of the reserve
     * @param borrowId The id of the borrow info
     * @param user The beneficiary of the repayment, getting his debt reduced
     * @param nft The nft corresponding to repayment
     * @param nftId The tokenId of the borrowed nft
     * @param amount The amount repaid
     **/
    event Repay(
        uint256 indexed reserveId,
        uint256 indexed borrowId,
        address user,
        address indexed nft,
        uint256 nftId,
        uint256 amount
    );

    event LiquidationCall(
        uint256 indexed reserveId,
        uint256 indexed borrowId,
        address indexed user,
        address nft,
        uint256 id,
        uint256 amount,
        uint256 time
    );

    event BidCall(
        uint256 indexed reserveId,
        uint256 indexed borrowId,
        address indexed user,
        uint256 amount,
        uint256 time
    );

    event ClaimCall(
        uint256 indexed reserveId,
        uint256 indexed borrowId,
        address indexed user,
        uint256 time
    );

    /**
     * @dev Emitted on rebalanceStableBorrowRate()
     * @param reserveId The id of the reserve
     * @param user The address of the user for which the rebalance has been executed
     **/
    event RebalanceStableBorrowRate(
        uint256 indexed reserveId,
        address indexed user
    );

    /**
     * @dev Emitted when the pause is triggered.
     */
    event Paused();

    /**
     * @dev Emitted when the pause is lifted.
     */
    event Unpaused();

    /**
     * @dev Emitted when new stable debt is increased
     * @param reserveId The id of the reserve
     * @param asset The address of nft
     * @param user The address of the user who triggered the minting
     * @param amount The amount minted
     * @param currentBalance The current balance of the user
     * @param balanceIncrease The increase in balance since the last action of the user
     * @param newRate The rate of the debt after the minting
     * @param newTotalSupply The new total supply of the stable debt token after the action
     **/
    event StableDebtIncrease(
        uint256 indexed reserveId,
        address indexed asset,
        address indexed user,
        uint256 amount,
        uint256 currentBalance,
        uint256 balanceIncrease,
        uint256 newRate,
        uint256 avgStableRate,
        uint256 newTotalSupply
    );

    /**
     * @dev Emitted when new stable debt is decreased
     * @param reserveId The id of the reserve
     * @param user The address of the user
     * @param amount The amount being burned
     * @param currentBalance The current balance of the user
     * @param balanceIncrease The the increase in balance since the last action of the user
     * @param avgStableRate The new average stable rate after the burning
     * @param newTotalSupply The new total supply of the stable debt token after the action
     **/
    event StableDebtDecrease(
        uint256 indexed reserveId,
        address indexed asset,
        address indexed user,
        uint256 amount,
        uint256 currentBalance,
        uint256 balanceIncrease,
        uint256 avgStableRate,
        uint256 newTotalSupply
    );

    /**
     * @dev Emitted when new varibale debt is increased
     * @param reserveId The id of the reserve
     * @param asset The address performing the nft
     * @param user The address of the user on which behalf minting has been performed
     * @param value The amount to be minted
     * @param index The last index of the reserve
     **/
    event VariableDebtIncrease(
        uint256 indexed reserveId,
        address indexed asset,
        address indexed user,
        uint256 value,
        uint256 index
    );

    /**
     * @dev Emitted when variable debt is decreased
     * @param reserveId The id of the reserve
     * @param asset The address of the nft
     * @param user The user which debt has been burned
     * @param amount The amount of debt being burned
     * @param index The index of the user
     **/
    event VariableDebtDecrease(
        uint256 indexed reserveId,
        address indexed asset,
        address indexed user,
        uint256 amount,
        uint256 index
    );

    event SetMinBorrowTime(uint40 time);

    /**
     * @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying kTokens.
     * @param reserveId The id of the reserve
     * @param onBehalfOf The beneficiary of the deposit, receiving the kTokens
     **/
    function deposit(uint256 reserveId, address onBehalfOf) external payable;

    /**
     * @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent kTokens owned
     * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
     * @param reserveId The id of the reserve
     * @param amount The underlying amount to be withdrawn
     *   - Send the value type(uint256).max in order to withdraw the whole kToken balance
     * @param to Address that will receive the underlying, same as msg.sender if the user
     *   wants to receive it on his own wallet, or a different address if the beneficiary is a
     *   different wallet
     * @return The final amount withdrawn
     **/
    function withdraw(
        uint256 reserveId,
        uint256 amount,
        address to
    ) external returns (uint256);

    /**
     * @dev Allows users to borrow an estimate `amount` of the reserve underlying asset according to the value of the nft
     * @param reserveId The id of the reserve
     * @param asset The address of the nft to be borrowed
     * @param nftId The tokenId of the nft to be borrowed
     * @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable
     * @param onBehalfOf The address that will recieve the borrow asset and debt token (must be msg.sender or the msg.sender must be punkGateway)
     **/
    function borrow(
        uint256 reserveId,
        address asset,
        uint256 nftId,
        uint256 interestRateMode,
        address onBehalfOf
    ) external returns (uint256);

    /**
     * @notice Repays a borrowed `amount` on a specific reserve
     * @param borrowId The id of the borrow to repay
     * @param onBehalfOf The address that will burn the debt token (must be msg.sender or the msg.sender must be punkGateway)
     * @return The final amount repaid
     **/
    function repay(
        uint256 borrowId,
        address onBehalfOf
    ) external payable returns (uint256);

    /**
     * @dev Rebalances the stable interest rate of a user to the current stable rate defined on the reserve.
     * - Users can be rebalanced if the following conditions are satisfied:
     *     1. Usage ratio is above 95%
     *     2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been
     *        borrowed at a stable rate and depositors are not earning enough
     * @param reserveId The id of the reserve
     * @param user The address of the user to be rebalanced
     **/
    function rebalanceStableBorrowRate(
        uint256 reserveId,
        address user
    ) external;

    /**
     * @dev Function to liquidate an expired borrow info.
     * @param borrowId The id of liquidate borrow target
     **/
    function liquidationCall(
        uint256 borrowId
    ) external payable;

    /**
     * @dev Function to bid for the liquidate auction.
     * @param borrowId The id of liquidate borrow target
     **/
    function bidCall(uint256 borrowId) external payable;

    /**
     * @dev Function to claim the liquidate NFT.
     * @param borrowId The id of liquidate borrow target
     **/
    function claimCall(uint256 borrowId) external;

    function claimCall(
        uint256 borrowId,
        address onBehalfOf
    ) external;

    /**
     * @dev Returns the list of user's borrowId
     * @param user The address of the user
     **/
    function getUserBorrowList(
        address user
    ) external view returns (uint256[] memory borrowIds);

    /**
     * @dev Returns the list of borrowId in auction
     **/
    function getAuctions() external view returns (uint256[] memory);

    /**
     * @dev Returns the list of user's borrowId
     * @param borrowId The id of the borrow info
     **/
    function getDebt(uint256 borrowId) external view returns (uint256 debt);

    function getInitialLockTime(
        uint256 reserveId
    ) external view returns (uint256);

    function enabledLiquidation(uint256 borrowId) external view returns (bool);

    function initReserve(
        address asset,
        address kTokenAddress,
        address stableDebtAddress,
        address variableDebtAddress,
        address interestRateStrategyAddress
    ) external;

    function updateReserveNFT(
        uint256 reserveId,
        address asset,
        bool flag
    ) external;

    function setReserveInterestRateStrategyAddress(
        uint256 reserveId,
        address rateStrategyAddress
    ) external;

    function burnLiquidity(uint256 reserveId, uint256 amount) external;

    function setConfiguration(
        uint256 reserveId,
        uint256 configuration
    ) external;

    /**
     * @dev Returns the configuration of the reserve
     * @param reserveId The id of the reserve
     * @return The configuration of the reserve
     **/
    function getConfiguration(
        uint256 reserveId
    ) external view returns (DataTypes.ReserveConfigurationMap memory);

    /**
     * @dev Returns the normalized income normalized income of the reserve
     * @param reserveId The id of the reserve
     * @return The reserve's normalized income
     */
    function getReserveNormalizedIncome(
        uint256 reserveId
    ) external view returns (uint256);

    /**
     * @dev Returns the normalized variable debt per unit of asset
     * @param reserveId The id of the reserve
     * @return The reserve normalized variable debt
     */
    function getReserveNormalizedVariableDebt(
        uint256 reserveId
    ) external view returns (uint256);

    /**
     * @dev Returns the state and configuration of the reserve
     * @param reserveId The id of the reserve
     * @return The state of the reserve
     **/
    function getReserveData(
        uint256 reserveId
    ) external view returns (DataTypes.ReserveData memory);

    function getReservesList() external view returns (address[] memory);

    function getBorrowInfo(
        uint256 borrowId
    ) external view returns (DataTypes.BorrowInfo memory);

    function setPause(bool val) external;

    function paused() external view returns (bool);

    function getReservesCount() external view returns (uint256);
}

interface IPriceOracle {
    function getPrice(address _nft) external returns (int);

    function getPrice_view(address _nft) external view returns (int);
}

// File: contracts/interfaces/IKyokoPoolAddressesProvider.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

/**
 * @title LendingPoolAddressesProvider contract
 * @dev Main registry of addresses part of or connected to the protocol, including permissioned roles
 * - Acting also as factory of proxies and admin of those, so with right to change its implementations
 * - Owned by the Kyoko Governance
 * @author Kyoko
 **/
interface IKyokoPoolAddressesProvider {
    event MarketIdSet(string newMarketId);
    event KyokoPoolUpdated(address indexed newAddress);
    event ConfigurationAdminUpdated(address indexed newAddress);
    event EmergencyAdminUpdated(address indexed newAddress);
    event KyokoPoolLiquidatorUpdated(address indexed newAddress);
    event KyokoPoolConfiguratorUpdated(address indexed newAddress);
    event KyokoPoolFactoryUpdated(address indexed newAddress);
    event RateStrategyUpdated(address indexed newAddress);
    event PriceOracleUpdated(address indexed newAddress);
    event AddressSet(bytes32 id, address indexed newAddress);
    event AddressRevoke(bytes32 id, address indexed oldAddress);

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

    function setMarketId(string calldata marketId) external;

    function setAddress(bytes32 id, address newAddress) external;

    function revokeAddress(bytes32 id, address oldAddress) external;

    function getAddress(bytes32 id) external view returns (address[] memory);

    function hasRole(bytes32 id, address account) external view returns (bool);

    function getKyokoPool() external view returns (address[] memory);

    function isKyokoPool(address account) external view returns (bool);

    function setKyokoPool(address pool) external;

    function getKyokoPoolLiquidator() external view returns (address[] memory);
    
    function isLiquidator(address account) external view returns (bool);

    function setKyokoPoolLiquidator(address liquidator) external;

    function getKyokoPoolConfigurator() external view returns (address[] memory);
    
    function isConfigurator(address account) external view returns (bool);

    function setKyokoPoolConfigurator(address configurator) external;

    function getKyokoPoolFactory() external view returns (address[] memory);
    
    function isFactory(address account) external view returns (bool);

    function setKyokoPoolFactory(address factory) external;

    function getPoolAdmin() external view returns (address[] memory);
    
    function isAdmin(address account) external view returns (bool);

    function setPoolAdmin(address admin) external;

    function getEmergencyAdmin() external view returns (address[] memory);
    
    function isEmergencyAdmin(address account) external view returns (bool);

    function setEmergencyAdmin(address admin) external;

    function getPriceOracle() external view returns (address[] memory);

    function isOracle(address account) external view returns (bool);

    function setPriceOracle(address priceOracle) external;

    function getRateStrategy() external view returns (address[] memory);

    function isStrategy(address account) external view returns (bool);

    function setRateStrategy(address rateStrategy) external;
}

// File: contracts/interfaces/IKyokoPoolConfigurator.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "../libraries/utils/DataTypes.sol";

interface IKyokoPoolConfigurator {
    /**
     * @dev Emitted when a reserve is initialized.
     * @param reserveId The id of the reserve
     * @param asset The address of the underlying nft asset of the reserve
     * @param kToken The address of the associated kToken contract
     * @param stableDebtToken The address of the associated stable rate debt token
     * @param variableDebtToken The address of the associated variable rate debt token
     * @param interestRateStrategyAddress The address of the interest rate strategy for the reserve
     **/
    event ReserveInitialized(
        uint256 indexed reserveId,
        address indexed asset,
        address indexed kToken,
        address stableDebtToken,
        address variableDebtToken,
        address interestRateStrategyAddress
    );

    /**
     * @dev Emitted when a reserve is activated
     * @param reserveId The id of the reserve
     **/
    event ReserveActivated(uint256 indexed reserveId);

    /**
     * @dev Emitted when a reserve is deactivated
     * @param reserveId The id of the reserve
     **/
    event ReserveDeactivated(uint256 indexed reserveId);

    /**
     * @dev Emitted when a reserve is frozen
     * @param reserveId The id of the reserve
     **/
    event ReserveFrozen(uint256 indexed reserveId);

    /**
     * @dev Emitted when a reserve is unfrozen
     * @param reserveId The id of the reserve
     **/
    event ReserveUnfrozen(uint256 indexed reserveId);

    /**
     * @dev Emitted when a reserve factor is updated
     * @param reserveId The id of the reserve
     * @param factor The new reserve factor
     **/
    event ReserveFactorChanged(uint256 indexed reserveId, uint256 factor);

    /**
     * @dev Emitted when a borrow ratio is updated
     * @param reserveId The id of the reserve
     * @param ratio The new borrow ratio
     **/
    event ReserveBorrowRatioChanged(uint256 indexed reserveId, uint256 ratio);

    /**
     * @dev Emitted when a fixed borrow perdio is updated
     * @param reserveId The id of the reserve
     * @param period The new fixed borrow period
     **/
    event ReservePeriodChanged(uint256 indexed reserveId, uint256 period);

    /**
     * @dev Emitted when a fixed borrow perdio is updated
     * @param reserveId The id of the reserve
     * @param time The new minimum borrow time
     **/
    event ReserveMinBorrowTimeChanged(uint256 indexed reserveId, uint256 time);

    /**
     * @dev Emitted when borrowing is enabled on a reserve
     * @param reserveId The id of the reserve
     * @param stableRateEnabled True if stable rate borrowing is enabled, false otherwise
     **/
    event BorrowingEnabledOnReserve(
        uint256 indexed reserveId,
        bool stableRateEnabled
    );

    /**
     * @dev Emitted when borrowing is disabled on a reserve
     * @param reserveId The id of the reserve
     **/
    event BorrowingDisabledOnReserve(uint256 indexed reserveId);

    /**
     * @dev Emitted when stable rate borrowing is enabled on a reserve
     * @param reserveId The id of the reserve
     **/
    event StableRateEnabledOnReserve(uint256 indexed reserveId);

    /**
     * @dev Emitted when stable rate borrowing is disabled on a reserve
     * @param reserveId The id of the reserve
     **/
    event StableRateDisabledOnReserve(uint256 indexed reserveId);

    /**
     * @dev Emitted when a reserve liquidation threshold is updated
     * @param reserveId The id of the reserve
     * @param threshold The new liquidation threshold of the reserve
     **/
    event ReserveLiquidationThresholdChanged(
        uint256 indexed reserveId,
        uint256 threshold
    );

    /**
     * @dev Emitted when a reserve liquidation duration is updated
     * @param reserveId The id of the reserve
     * @param duration The duration of the liquidation
     **/
    event ReserveLiquidationDurationChanged(
        uint256 indexed reserveId,
        uint256 duration
    );

    /**
     * @dev Emitted when a reserve bid duration is updated
     * @param reserveId The id of the reserve
     * @param duration The duration of each auction
     **/
    event ReserveBidDurationChanged(
        uint256 indexed reserveId,
        uint256 duration
    );

    /**
     * @dev Emitted when a reserve initial liquidity lock time is updated
     * @param reserveId The id of the reserve
     * @param lockTime The lock time
     **/
    event ReserveLockTimeChanged(uint256 indexed reserveId, uint256 lockTime);

    /**
     * @dev Emitted when a reserve type is updated
     * @param reserveId The id of the reserve
     * @param reserveType The reserve type
     **/
    event ReserveTypeChanged(uint256 indexed reserveId, uint256 reserveType);

    /**
     * @dev Emitted when a reserve initial liquidity is burned
     * @param reserveId The id of the reserve
     * @param amount The burned amount
     **/
    event ReserveInitialLiquidityBurned(
        uint256 indexed reserveId,
        uint256 amount
    );

    /**
     * @dev Emitted when a reserve interest strategy contract is updated
     * @param reserveId The id of the reserve
     * @param strategy The new address of the interest strategy contract
     **/
    event ReserveInterestRateStrategyChanged(
        uint256 indexed reserveId,
        address strategy
    );

    /**
     * @dev Emitted when an kToken implementation is upgraded
     * @param asset The address of the underlying asset of the reserve
     * @param proxy The kToken proxy address
     * @param implementation The new kToken implementation
     **/
    event kTokenUpgraded(
        address indexed asset,
        address indexed proxy,
        address indexed implementation
    );

    /**
     * @dev Emitted when the implementation of a stable debt token is upgraded
     * @param asset The address of the underlying asset of the reserve
     * @param proxy The stable debt token proxy address
     * @param implementation The new kToken implementation
     **/
    event StableDebtTokenUpgraded(
        address indexed asset,
        address indexed proxy,
        address indexed implementation
    );

    /**
     * @dev Emitted when the implementation of a variable debt token is upgraded
     * @param asset The address of the underlying asset of the reserve
     * @param proxy The variable debt token proxy address
     * @param implementation The new kToken implementation
     **/
    event VariableDebtTokenUpgraded(
        address indexed asset,
        address indexed proxy,
        address indexed implementation
    );

    function factoryInitReserve(
        DataTypes.InitReserveInput memory input,
        DataTypes.RateStrategyInput memory rateInput
    ) external;
}

// File: contracts/interfaces/IScaledBalanceToken.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

interface IScaledBalanceToken {
  /**
   * @dev Returns the scaled balance of the user. The scaled balance is the sum of all the
   * updated stored balance divided by the reserve's liquidity index at the moment of the update
   * @param user The user whose balance is calculated
   * @return The scaled balance of the user
   **/
  function scaledBalanceOf(address user) external view returns (uint256);

  /**
   * @dev Returns the scaled balance of the user and the scaled total supply.
   * @param user The address of the user
   * @return The scaled balance of the user
   * @return The scaled balance and the scaled total supply
   **/
  function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);

  /**
   * @dev Returns the scaled total supply of the variable debt token. Represents sum(debt/index)
   * @return The scaled total supply
   **/
  function scaledTotalSupply() external view returns (uint256);
}

// File: contracts/interfaces/IStableDebtToken.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "./IInitializableDebtToken.sol";

/**
 * @title IStableDebtToken
 * @notice Defines the interface for the stable debt token
 * @dev It does not inherit from IERC20 to save in code size
 * @author Kyoko
 **/

interface IStableDebtToken is IInitializableDebtToken {
  /**
   * @dev Emitted when new stable debt is minted
   * @param user The address of the user who triggered the minting
   * @param onBehalfOf The recipient of stable debt tokens
   * @param amount The amount minted
   * @param currentBalance The current balance of the user
   * @param balanceIncrease The increase in balance since the last action of the user
   * @param newRate The rate of the debt after the minting
   * @param avgStableRate The new average stable rate after the minting
   * @param newTotalSupply The new total supply of the stable debt token after the action
   **/
  event Mint(
    address indexed user,
    address indexed onBehalfOf,
    uint256 amount,
    uint256 currentBalance,
    uint256 balanceIncrease,
    uint256 newRate,
    uint256 avgStableRate,
    uint256 newTotalSupply
  );

  /**
   * @dev Emitted when new stable debt is burned
   * @param user The address of the user
   * @param amount The amount being burned
   * @param currentBalance The current balance of the user
   * @param balanceIncrease The the increase in balance since the last action of the user
   * @param avgStableRate The new average stable rate after the burning
   * @param newTotalSupply The new total supply of the stable debt token after the action
   **/
  event Burn(
    address indexed user,
    uint256 amount,
    uint256 currentBalance,
    uint256 balanceIncrease,
    uint256 avgStableRate,
    uint256 newTotalSupply
  );

  /**
   * @dev Mints debt token to the `onBehalfOf` address.
   * - The resulting rate is the weighted average between the rate of the new debt
   * and the rate of the previous debt
   * @param user The address receiving the borrowed underlying, being the delegatee in case
   * of credit delegate, or same as `onBehalfOf` otherwise
   * @param onBehalfOf The address receiving the debt tokens
   * @param amount The amount of debt tokens to mint
   * @param rate The rate of the debt being minted
   **/
  function mint(
    address user,
    address onBehalfOf,
    uint256 amount,
    uint256 rate
  ) external returns (bool);

  /**
   * @dev Burns debt of `user`
   * - The resulting rate is the weighted average between the rate of the new debt
   * and the rate of the previous debt
   * @param user The address of the user getting his debt burned
   * @param amount The amount of debt tokens getting burned
   **/
  function burn(address user, uint256 amount) external;

  /**
   * @dev Returns the average rate of all the stable rate loans.
   * @return The average stable rate
   **/
  function getAverageStableRate() external view returns (uint256);

  /**
   * @dev Returns the stable rate of the user debt
   * @return The stable rate of the user
   **/
  function getUserStableRate(address user) external view returns (uint256);

  /**
   * @dev Returns the timestamp of the last update of the user
   * @return The timestamp
   **/
  function getUserLastUpdated(address user) external view returns (uint40);

  /**
   * @dev Returns the principal, the total supply and the average stable rate
   **/
  function getSupplyData()
    external
    view
    returns (
      uint256,
      uint256,
      uint256,
      uint40
    );

  /**
   * @dev Returns the timestamp of the last update of the total supply
   * @return The timestamp
   **/
  function getTotalSupplyLastUpdated() external view returns (uint40);

  /**
   * @dev Returns the total supply and the average stable rate
   **/
  function getTotalSupplyAndAvgRate() external view returns (uint256, uint256);

  /**
   * @dev Returns the principal debt balance of the user
   * @return The debt balance of the user since the last burn/mint action
   **/
  function principalBalanceOf(address user) external view returns (uint256);

  function balanceOfAmount(address account, uint256 amount) external view returns (uint256);
}

// File: contracts/interfaces/IVariableDebtToken.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "./IScaledBalanceToken.sol";
import "./IInitializableDebtToken.sol";

/**
 * @title IVariableDebtToken
 * @author Kyoko
 * @notice Defines the basic interface for a variable debt token.
 **/
interface IVariableDebtToken is IScaledBalanceToken, IInitializableDebtToken {
  /**
   * @dev Emitted after the mint action
   * @param from The address performing the mint
   * @param onBehalfOf The address of the user on which behalf minting has been performed
   * @param value The amount to be minted
   * @param index The last index of the reserve
   **/
  event Mint(address indexed from, address indexed onBehalfOf, uint256 value, uint256 index);

  /**
   * @dev Mints debt token to the `onBehalfOf` address
   * @param user The address receiving the borrowed underlying, being the delegatee in case
   * of credit delegate, or same as `onBehalfOf` otherwise
   * @param onBehalfOf The address receiving the debt tokens
   * @param amount The amount of debt being minted
   * @param index The variable debt index of the reserve
   * @return `true` if the the previous balance of the user is 0
   **/
  function mint(
    address user,
    address onBehalfOf,
    uint256 amount,
    uint256 index
  ) external returns (bool);

  /**
   * @dev Emitted when variable debt is burnt
   * @param user The user which debt has been burned
   * @param amount The amount of debt being burned
   * @param index The index of the user
   **/
  event Burn(address indexed user, uint256 amount, uint256 index);

  /**
   * @dev Burns user variable debt
   * @param user The user which debt is burnt
   * @param index The variable debt index of the reserve
   **/
  function burn(
    address user,
    uint256 amount,
    uint256 index
  ) external;

  function balanceOfAmount(address user, uint256 amount) external view returns (uint256);
}

// File: contracts/libraries/utils/DataTypes.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

library DataTypes {
    struct ReserveData {
        //stores the reserve configuration
        ReserveConfigurationMap configuration;
        //the liquidity index. Expressed in ray
        uint128 liquidityIndex;
        // variable borrow index. Expressed in ray
        uint128 variableBorrowIndex;
        //the current supply rate. Expressed in ray
        uint128 currentLiquidityRate;
        // the current variable borrow rate. Expressed in ray
        uint128 currentVariableBorrowRate;
        //the current stable borrow rate. Expressed in ray
        uint128 currentStableBorrowRate;
        uint40 lastUpdateTimestamp;
        //tokens addresses
        address kTokenAddress;
        address stableDebtTokenAddress;
        address variableDebtTokenAddress;
        //address of the interest rate strategy
        address interestRateStrategyAddress;
        //the id of the reserve. Represents the position in the list of the active reserves
        uint256 id;
    }

    struct ReserveConfigurationMap {
        // bit 0-15: factor
        // bit 16-31: borrow ratio
        // bit 32-71: period
        // bit 72-111: min borrow time
        // bit 112: reserve is active
        // bit 113-128: Liq. threshold
        // bit 129: borrowing is enabled
        // bit 130: stable rate borrowing enabled
        // bit 131-154: liquidation duration
        // bit 155-178: auction duration
        // bit 179: reserve is frozen
        // bit 180-211: initial liquidity lock period
        // bit 212-219: reserve type
        uint256 data;
    }

    struct Request {
        address user;
        address nft;
        uint256 id;
        InterestRateMode rateMode;
        uint256 reserveId;
    }

    enum Status {
        BORROW,
        REPAY,
        AUCTION,
        WITHDRAW
    }

    enum InterestRateMode {
        NONE,
        STABLE,
        VARIABLE
    }

    struct BorrowInfo {
        uint256 reserveId;
        address nft;
        uint256 nftId;
        address user;
        uint64 startTime;
        uint256 principal;
        uint256 borrowId;
        uint64 liquidateTime;
        Status status;
        InterestRateMode rateMode;
    }

    struct Auction {
        // ID for the Noun (ERC721 token ID)
        uint256 borrowId;
        // The current highest bid amount
        uint256 amount;
        // The time that the auction started
        uint256 startTime;
        // The time that the auction is scheduled to end
        uint256 endTime;
        // The address of the current highest bid
        address payable bidder;
        // Whether or not the auction has been settled
        bool settled;
    }

    struct InitReserveInput {
        uint256 reserveId;
        address kTokenImpl;
        address stableDebtTokenImpl;
        address variableDebtTokenImpl;
        address interestRateStrategyAddress;
        address underlyingAsset;
        address treasury;
        uint16 factor;
        uint16 borrowRatio;
        uint40 period;
        uint40 minBorrowTime;
        uint16 liqThreshold;
        uint24 liqDuration;
        uint24 bidDuration;
        uint32 lockTime;
        bool stableBorrowed;
    }

    struct RateStrategyInput {
        uint256 reserveId;
        uint256 optimalUtilizationRate;
        uint256 baseVariableBorrowRate;
        uint256 variableSlope1;
        uint256 variableSlope2;
        uint256 baseStableBorrowRate;
        uint256 stableSlope1;
        uint256 stableSlope2;
    }
    
    struct Rate {
        /**
         * @dev this constant represents the utilization rate at which the pool aims to obtain most competitive borrow rates.
         * Expressed in ray
         **/
        uint256 optimalUtilizationRate;
        /**
         * @dev This constant represents the excess utilization rate above the optimal. It's always equal to
         * 1-optimal utilization rate. Added as a constant here for gas optimizations.
         * Expressed in ray
         **/
        uint256 excessUtilizationRate;
        // Base variable borrow rate when Utilization rate = 0. Expressed in ray
        uint256 baseVariableBorrowRate;
        // Slope of the variable interest curve when utilization rate > 0 and <= OPTIMAL_UTILIZATION_RATE. Expressed in ray
        uint256 variableRateSlope1;
        // Slope of the variable interest curve when utilization rate > OPTIMAL_UTILIZATION_RATE. Expressed in ray
        uint256 variableRateSlope2;
        // Base stable borrow rate when Utilization rate = 0. Expressed in ray
        uint256 baseStableBorrowRate;
        // Slope of the stable interest curve when utilization rate > 0 and <= OPTIMAL_UTILIZATION_RATE. Expressed in ray
        uint256 stableRateSlope1;
        // Slope of the stable interest curve when utilization rate > OPTIMAL_UTILIZATION_RATE. Expressed in ray
        uint256 stableRateSlope2;
    }
}

// File: contracts/libraries/utils/Errors.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

/**
 * @title Errors library
 * @author Kyoko
 * @notice Defines the error messages emitted by the different contracts of the Kyoko protocol
 * @dev Error messages prefix glossary:
 *  - VL = ValidationLogic
 *  - MATH = Math libraries
 *  - CT = Common errors between tokens (KToken, VariableDebtToken and StableDebtToken)
 *  - KT = KToken
 *  - SDT = StableDebtToken
 *  - VDT = VariableDebtToken
 *  - KP = KyokoPool
 *  - KF = KyokoFactory
 *  - KPC = KyokoPoolConfiguration
 *  - RL = ReserveLogic
 *  - KPCM = KyokoPoolCollateralManager
 *  - P = Pausable
 */
library Errors {
  //common errors
  string public constant CALLER_NOT_POOL_ADMIN = '25'; // 'The caller must be the pool admin'
  string public constant BORROW_ALLOWANCE_NOT_ENOUGH = '26'; // User borrows on behalf, but allowance are too small
  string public constant ERROR = '27'; // User borrows on behalf, but allowance are too small

  //contract specific errors
  string public constant VL_INVALID_AMOUNT = '1'; // 'Amount must be greater than 0'
  string public constant VL_NO_ACTIVE_RESERVE = '2'; // 'Action requires an active reserve'
  string public constant VL_RESERVE_FROZEN = '3'; // 'Action cannot be performed because the reserve is frozen'
  string public constant VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE = '4'; // 'User cannot withdraw more than the available balance'
  string public constant VL_INVALID_INTEREST_RATE_MODE_SELECTED = '5'; // 'Invalid interest rate mode selected'
  string public constant VL_BORROWING_NOT_ENABLED = '6'; // 'Borrowing is not enabled'
  string public constant VL_STABLE_BORROWING_NOT_ENABLED = '7'; // stable borrowing not enabled
  string public constant VL_NO_DEBT_OF_SELECTED_TYPE = '8'; // 'for repayment of stable debt, the user needs to have stable debt, otherwise, he needs to have variable debt'
  string public constant VL_NOT_NFT_OWNER = '9'; // 'User is not the owner of the nft'
  string public constant VL_NOT_SUPPORT = '10'; // 'User's nft for borrow is not support'
  string public constant VL_TOO_EARLY = '11'; // 'Action is earlier than requested'
  string public constant VL_TOO_LATE = '12'; // 'Action is later than requested'
  string public constant VL_BAD_STATUS = '13'; // 'Action with wrong borrow status'
  string public constant VL_INVALID_USER = '14'; // 'User is not borrow owner'
  string public constant VL_AUCTION_ALREADY_SETTLED = '15'; // 'Auction is already done'
  string public constant VL_BAD_PRICE_TO_REPAY = '16'; // 'The floor price below liquidation price'
  string public constant LP_NOT_ENOUGH_STABLE_BORROW_BALANCE = '31'; // 'User does not have any stable rate loan for this reserve'
  string public constant LP_INTEREST_RATE_REBALANCE_CONDITIONS_NOT_MET = '32'; // 'Interest rate rebalance conditions were not met'
  string public constant LP_LIQUIDATION_CALL_FAILED = '33'; // 'Liquidation call failed'
  string public constant LP_REQUESTED_AMOUNT_TOO_SMALL = '34'; // 'The requested amount is too small for an action.'
  string public constant LP_CALLER_NOT_KYOKO_POOL_CONFIGURATOR = '35'; // 'The caller of the function is Kyoko pool configurator'
  string public constant LP_CALLER_NOT_KYOKO_POOL_ORACLE = '36'; // 'The caller of the function is not the Kyoko pool oracle'
  string public constant LP_CALLER_NOT_KYOKO_POOL_FACTORY = '37'; // 'The caller of the function is not the Kyoko pool factory'
  string public constant LP_NFT_ALREADY_EXIST = '38'; // 'The initial reserve nft is already exist'
  string public constant LP_WETH_TRANSFER_FAILED = '39'; // 'Failed to transfer eth and weth'
  string public constant LP_BORROW_FAILED = '41'; // 'Can't be borrowed'
  string public constant LP_LIQUIDITY_INSUFFICIENT = '42'; // 'Insufficient pool balance'
  string public constant LP_IS_PAUSED = '43'; // 'Pool is paused'
  string public constant LP_NO_MORE_RESERVES_ALLOWED = '44';
  string public constant LP_NOT_CONTRACT = '45';
  string public constant LP_NFT_NOT_SUPPORT = '46';
  string public constant CT_CALLER_MUST_BE_KYOKO_POOL = '51'; // 'The caller of this function must be a Kyoko pool'
  string public constant RL_RESERVE_ALREADY_INITIALIZED = '52'; // 'Reserve has already been initialized'
  string public constant KPC_RESERVE_LIQUIDITY_NOT_0 = '53'; // 'The liquidity of the reserve needs to be 0'
  string public constant KPC_CALLER_NOT_EMERGENCY_ADMIN = '54'; // 'The caller must be the emergency admin'
  string public constant KPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD = '55'; // 'Health factor is not below the threshold'
  string public constant KPCM_LIQUIDATION_DISABLED = '56'; // 'Health factor is not below the threshold'
  string public constant KPCM_NO_ERRORS = '57'; // 'No errors'
  string public constant MATH_MULTIPLICATION_OVERFLOW = '58';
  string public constant MATH_ADDITION_OVERFLOW = '59';
  string public constant MATH_DIVISION_BY_ZERO = '60';
  string public constant RL_LIQUIDITY_INDEX_OVERFLOW = '61'; //  Liquidity index overflows uint128
  string public constant RL_VARIABLE_BORROW_INDEX_OVERFLOW = '62'; //  Variable borrow index overflows uint128
  string public constant RL_LIQUIDITY_RATE_OVERFLOW = '63'; //  Liquidity rate overflows uint128
  string public constant RL_VARIABLE_BORROW_RATE_OVERFLOW = '64'; //  Variable borrow rate overflows uint128
  string public constant RL_STABLE_BORROW_RATE_OVERFLOW = '65'; //  Stable borrow rate overflows uint128
  string public constant CT_INVALID_MINT_AMOUNT = '66'; //invalid amount to mint
  string public constant CT_INVALID_BURN_AMOUNT = '67'; //invalid amount to burn
  string public constant RC_INVALID_RESERVE_FACTOR = '71';
  string public constant RC_INVALID_BORROW_RATIO = '72';
  string public constant RC_INVALID_PERIOD = '73';
  string public constant RC_INVALID_MIN_BORROW_TIME = '74';
  string public constant RC_INVALID_LIQ_THRESHOLD = '75';
  string public constant RC_INVALID_LIQ_TIME = '76';
  string public constant RC_INVALID_BID_TIME = '77';
  string public constant SDT_STABLE_DEBT_OVERFLOW = '81';
  string public constant SDT_BURN_EXCEEDS_BALANCE = '82';
  string public constant SDT_CREATION_FAILED = '83';
  string public constant VDT_CREATION_FAILED = '84';
  string public constant KF_LIQUIDITY_INSUFFICIENT = '85';
  string public constant KT_CREATION_FAILED = '86';
  string public constant KT_ERROR_CREATOR = '87';
  string public constant KT_INITIAL_LIQUIDITY_LOCK = '88';
}

// File: contracts/libraries/utils/WadRayMath.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "./Errors.sol";

/**
 * @title WadRayMath library
 * @author Kyoko
 * @dev Provides mul and div function for wads (decimal numbers with 18 digits precision) and rays (decimals with 27 digits)
 **/

library WadRayMath {
  uint256 internal constant WAD = 1e18;
  uint256 internal constant halfWAD = WAD / 2;

  uint256 internal constant RAY = 1e27;
  uint256 internal constant halfRAY = RAY / 2;

  uint256 internal constant WAD_RAY_RATIO = 1e9;

  /**
   * @return One ray, 1e27
   **/
  function ray() internal pure returns (uint256) {
    return RAY;
  }

  /**
   * @return One wad, 1e18
   **/

  function wad() internal pure returns (uint256) {
    return WAD;
  }

  /**
   * @return Half ray, 1e27/2
   **/
  function halfRay() internal pure returns (uint256) {
    return halfRAY;
  }

  /**
   * @return Half ray, 1e18/2
   **/
  function halfWad() internal pure returns (uint256) {
    return halfWAD;
  }

  /**
   * @dev Multiplies two wad, rounding half up to the nearest wad
   * @param a Wad
   * @param b Wad
   * @return The result of a*b, in wad
   **/
  function wadMul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0 || b == 0) {
      return 0;
    }

    require(a <= (type(uint256).max - halfWAD) / b, Errors.MATH_MULTIPLICATION_OVERFLOW);

    return (a * b + halfWAD) / WAD;
  }

  /**
   * @dev Divides two wad, rounding half up to the nearest wad
   * @param a Wad
   * @param b Wad
   * @return The result of a/b, in wad
   **/
  function wadDiv(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b != 0, Errors.MATH_DIVISION_BY_ZERO);
    uint256 halfB = b / 2;

    require(a <= (type(uint256).max - halfB) / WAD, Errors.MATH_MULTIPLICATION_OVERFLOW);

    return (a * WAD + halfB) / b;
  }

  /**
   * @dev Multiplies two ray, rounding half up to the nearest ray
   * @param a Ray
   * @param b Ray
   * @return The result of a*b, in ray
   **/
  function rayMul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0 || b == 0) {
      return 0;
    }

    require(a <= (type(uint256).max - halfRAY) / b, Errors.MATH_MULTIPLICATION_OVERFLOW);

    return (a * b + halfRAY) / RAY;
  }

  /**
   * @dev Divides two ray, rounding half up to the nearest ray
   * @param a Ray
   * @param b Ray
   * @return The result of a/b, in ray
   **/
  function rayDiv(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b != 0, Errors.MATH_DIVISION_BY_ZERO);
    uint256 halfB = b / 2;

    require(a <= (type(uint256).max - halfB) / RAY, Errors.MATH_MULTIPLICATION_OVERFLOW);

    return (a * RAY + halfB) / b;
  }

  /**
   * @dev Casts ray down to wad
   * @param a Ray
   * @return a casted to wad, rounded half up to the nearest wad
   **/
  function rayToWad(uint256 a) internal pure returns (uint256) {
    uint256 halfRatio = WAD_RAY_RATIO / 2;
    uint256 result = halfRatio + a;
    require(result >= halfRatio, Errors.MATH_ADDITION_OVERFLOW);

    return result / WAD_RAY_RATIO;
  }

  /**
   * @dev Converts wad up to ray
   * @param a Wad
   * @return a converted in ray
   **/
  function wadToRay(uint256 a) internal pure returns (uint256) {
    uint256 result = a * WAD_RAY_RATIO;
    require(result / WAD_RAY_RATIO == a, Errors.MATH_MULTIPLICATION_OVERFLOW);
    return result;
  }
}

// File: contracts/tokens/BasicERC20.sol



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

import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";

/**
 * @title ERC20
 * @notice Basic ERC20 implementation
 **/
abstract contract BasicERC20 is ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable
{

    mapping(address => uint256) internal _balances;

    mapping(address => mapping(address => uint256)) private _allowances;
    uint256 internal _totalSupply;
    string private _name;
    string private _symbol;
    uint8 private _decimals;

    constructor(
        string memory name,
        string memory symbol,
        uint8 decimals
    ) internal {
        _name = name;
        _symbol = symbol;
        _decimals = decimals;
    }

    /**
     * @return The name of the token
     **/
    function name() public view override returns (string memory) {
        return _name;
    }

    /**
     * @return The symbol of the token
     **/
    function symbol() public view override returns (string memory) {
        return _symbol;
    }

    /**
     * @return The decimals of the token
     **/
    function decimals() public view override returns (uint8) {
        return _decimals;
    }

    /**
     * @return The total supply of the token
     **/
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @return The balance of the token
     **/
    function balanceOf(address account)
        public
        view
        virtual
        override
        returns (uint256)
    {
        return _balances[account];
    }

    /**
     * @dev Executes a transfer of tokens from _msgSender() to recipient
     * @param recipient The recipient of the tokens
     * @param amount The amount of tokens being transferred
     * @return `true` if the transfer succeeds, `false` otherwise
     **/
    function transfer(address recipient, uint256 amount)
        public
        virtual
        override
        returns (bool)
    {
        _transfer(_msgSender(), recipient, amount);
        emit Transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev Returns the allowance of spender on the tokens owned by owner
     * @param owner The owner of the tokens
     * @param spender The user allowed to spend the owner's tokens
     * @return The amount of owner's tokens spender is allowed to spend
     **/
    function allowance(address owner, address spender)
        public
        view
        virtual
        override
        returns (uint256)
    {
        return _allowances[owner][spender];
    }

    /**
     * @dev Allows `spender` to spend the tokens owned by _msgSender()
     * @param spender The user allowed to spend _msgSender() tokens
     * @return `true`
     **/
    function approve(address spender, uint256 amount)
        public
        virtual
        override
        returns (bool)
    {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev Executes a transfer of token from sender to recipient, if _msgSender() is allowed to do so
     * @param sender The owner of the tokens
     * @param recipient The recipient of the tokens
     * @param amount The amount of tokens being transferred
     * @return `true` if the transfer succeeds, `false` otherwise
     **/
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        require(_allowances[sender][_msgSender()] >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(
            sender,
            _msgSender(),
            _allowances[sender][_msgSender()] - amount
        );
        emit Transfer(sender, recipient, amount);
        return true;
    }

    /**
     * @dev Increases the allowance of spender to spend _msgSender() tokens
     * @param spender The user allowed to spend on behalf of _msgSender()
     * @param addedValue The amount being added to the allowance
     * @return `true`
     **/
    function increaseAllowance(address spender, uint256 addedValue)
        public
        virtual
        returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender] + addedValue
        );
        return true;
    }

    /**
     * @dev Decreases the allowance of spender to spend _msgSender() tokens
     * @param spender The user allowed to spend on behalf of _msgSender()
     * @param subtractedValue The amount being subtracted to the allowance
     * @return `true`
     **/
    function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        virtual
        returns (bool)
    {
        require(_allowances[_msgSender()][spender] >= subtractedValue, "ERC20: decreased allowance below zero");
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender] - subtractedValue
        );
        return true;
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 oldSenderBalance = _balances[sender];
        require(oldSenderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = oldSenderBalance - amount;
        _balances[recipient] = _balances[recipient] + amount;
    }

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        uint256 oldTotalSupply = _totalSupply;
        _totalSupply = oldTotalSupply + amount;

        uint256 oldAccountBalance = _balances[account];
        _balances[account] = oldAccountBalance + amount;
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 oldTotalSupply = _totalSupply;
        _totalSupply = oldTotalSupply - amount;

        uint256 oldAccountBalance = _balances[account];
        require(oldAccountBalance >= amount, "ERC20: burn amount exceeds balance");
        _balances[account] = oldAccountBalance - amount;
    }

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _setName(string memory newName) internal {
        _name = newName;
    }

    function _setSymbol(string memory newSymbol) internal {
        _symbol = newSymbol;
    }

    function _setDecimals(uint8 newDecimals) internal {
        _decimals = newDecimals;
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}

// File: contracts/tokens/DebtTokenBase.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "../libraries/utils/Errors.sol";
import "../interfaces/IKyokoPool.sol";
import "../interfaces/ICreditDelegationToken.sol";
import "./BasicERC20.sol";

/**
 * @title DebtTokenBase
 * @notice Base contract for different types of debt tokens, like StableDebtToken or VariableDebtToken
 * @author Kyoko
 */

abstract contract DebtTokenBase is
  BasicERC20('DEBTTOKEN_IMPL', 'DEBTTOKEN_IMPL', 0),
  ICreditDelegationToken
{
  mapping(address => mapping(address => uint256)) internal _borrowAllowances;

  /**
   * @dev Only lending pool can call functions marked by this modifier
   **/
  modifier onlyKyokoPool {
    require(_msgSender() == address(_getKyokoPool()), Errors.CT_CALLER_MUST_BE_KYOKO_POOL);
    _;
  }

  /**
   * @dev delegates borrowing power to a user on the specific debt token
   * @param delegatee the address receiving the delegated borrowing power
   * @param amount the maximum amount being delegated. Delegation will still
   * respect the liquidation constraints (even if delegated, a delegatee cannot
   * force a delegator HF to go below 1)
   **/
  function approveDelegation(address delegatee, uint256 amount) external override {
    _borrowAllowances[_msgSender()][delegatee] = amount;
    emit BorrowAllowanceDelegated(_msgSender(), delegatee, _getUnderlyingAssetAddress(), amount);
  }

  /**
   * @dev returns the borrow allowance of the user
   * @param fromUser The user to giving allowance
   * @param toUser The user to give allowance to
   * @return the current allowance of toUser
   **/
  function borrowAllowance(address fromUser, address toUser)
    external
    view
    override
    returns (uint256)
  {
    return _borrowAllowances[fromUser][toUser];
  }

  /**
   * @dev Being non transferrable, the debt token does not implement any of the
   * standard ERC20 functions for transfer and allowance.
   **/
  function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
    recipient;
    amount;
    revert('TRANSFER_NOT_SUPPORTED');
  }

  function allowance(address owner, address spender)
    public
    view
    virtual
    override
    returns (uint256)
  {
    owner;
    spender;
    revert('ALLOWANCE_NOT_SUPPORTED');
  }

  function approve(address spender, uint256 amount) public virtual override returns (bool) {
    spender;
    amount;
    revert('APPROVAL_NOT_SUPPORTED');
  }

  function transferFrom(
    address sender,
    address recipient,
    uint256 amount
  ) public virtual override returns (bool) {
    sender;
    recipient;
    amount;
    revert('TRANSFER_NOT_SUPPORTED');
  }

  function increaseAllowance(address spender, uint256 addedValue)
    public
    virtual
    override
    returns (bool)
  {
    spender;
    addedValue;
    revert('ALLOWANCE_NOT_SUPPORTED');
  }

  function decreaseAllowance(address spender, uint256 subtractedValue)
    public
    virtual
    override
    returns (bool)
  {
    spender;
    subtractedValue;
    revert('ALLOWANCE_NOT_SUPPORTED');
  }

  function _decreaseBorrowAllowance(
    address delegator,
    address delegatee,
    uint256 amount
  ) internal {
      require(_borrowAllowances[delegator][delegatee] >= amount, Errors.BORROW_ALLOWANCE_NOT_ENOUGH);
    uint256 newAllowance =
      _borrowAllowances[delegator][delegatee] - amount;

    _borrowAllowances[delegator][delegatee] = newAllowance;

    emit BorrowAllowanceDelegated(delegator, delegatee, _getUnderlyingAssetAddress(), newAllowance);
  }

  function _getUnderlyingAssetAddress() internal view virtual returns (address);

  function _getKyokoPool() internal view virtual returns (IKyokoPool);

  function _getReserveId() internal view virtual returns (uint256);
}

// File: contracts/tokens/VariableDebtToken.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.18;

import "../libraries/utils/WadRayMath.sol";
import "../libraries/utils/Errors.sol";
import "../interfaces/IVariableDebtToken.sol";
import "../interfaces/IKyokoPool.sol";
import "../interfaces/IKyokoPoolAddressesProvider.sol";
import "./DebtTokenBase.sol";

/**
 * @title VariableDebtToken
 * @notice Implements a variable debt token to track the borrowing positions of users
 * at variable rate mode
 * @author Kyoko
 **/
contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
  using WadRayMath for uint256;

  uint256 public constant DEBT_TOKEN_REVISION = 0x1;

  IKyokoPoolAddressesProvider internal _addressesProvider;
  address internal _underlyingAsset;
  uint256 internal _reserveId;

  constructor(
    IKyokoPoolAddressesProvider provider,
    uint256 reserveId,
    address underlyingAsset,
    uint8 debtTokenDecimals,
    string memory debtTokenName,
    string memory debtTokenSymbol
  ) initializer {
    _setName(debtTokenName);
    _setSymbol(debtTokenSymbol);
    _setDecimals(debtTokenDecimals);

    _addressesProvider = provider;
    _reserveId = reserveId;
    _underlyingAsset = underlyingAsset;

    emit Initialized(
      underlyingAsset,
      _addressesProvider.getKyokoPool()[0],
      reserveId,
      debtTokenDecimals,
      debtTokenName,
      debtTokenSymbol
    );
  }

  /**
   * @dev Initializes the debt token.
   * @param provider The address of the address provider where this stableToken will be used
   * @param reserveId The id of the reserve
   * @param underlyingAsset The address of the underlying asset of this kToken (E.g. WETH for hWETH)
   * @param debtTokenDecimals The decimals of the debtToken, same as the underlying asset's
   * @param debtTokenName The name of the token
   * @param debtTokenSymbol The symbol of the token
   */
  function initialize(
    IKyokoPoolAddressesProvider provider,
    uint256 reserveId,
    address underlyingAsset,
    uint8 debtTokenDecimals,
    string memory debtTokenName,
    string memory debtTokenSymbol
  ) public override initializer {
    _setName(debtTokenName);
    _setSymbol(debtTokenSymbol);
    _setDecimals(debtTokenDecimals);

    _addressesProvider = provider;
    _reserveId = reserveId;
    _underlyingAsset = underlyingAsset;

    emit Initialized(
      underlyingAsset,
      _addressesProvider.getKyokoPool()[0],
      reserveId,
      debtTokenDecimals,
      debtTokenName,
      debtTokenSymbol
    );
  }

  /**
   * @dev Calculates the accumulated debt balance of the user
   * @return The debt balance of the user
   **/
  function balanceOf(address user) public view virtual override returns (uint256) {
    uint256 scaledBalance = super.balanceOf(user);

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

    IKyokoPool pool = _getKyokoPool();
    return scaledBalance.rayMul(pool.getReserveNormalizedVariableDebt(_reserveId));
  }

  /**
   * @dev Calculates the specific debt balance of the user
   * @return The debt balance of the user
   **/
  function balanceOfAmount(address user, uint256 amount) public view virtual override returns (uint256) {
    uint256 original = super.balanceOf(user);
    uint256 scaledBalance = amount > original ? original : amount;

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

    IKyokoPool pool = _getKyokoPool();
    return scaledBalance.rayMul(pool.getReserveNormalizedVariableDebt(_reserveId));
  }

  /**
   * @dev Mints debt token to the `onBehalfOf` address
   * -  Only callable by the LendingPool
   * @param user The address receiving the borrowed underlying, being the delegatee in case
   * of credit delegate, or same as `onBehalfOf` otherwise
   * @param onBehalfOf The address receiving the debt tokens
   * @param amount The amount of debt being minted
   * @param index The variable debt index of the reserve
   * @return `true` if the the previous balance of the user is 0
   **/
  function mint(
    address user,
    address onBehalfOf,
    uint256 amount,
    uint256 index
  ) external override onlyKyokoPool returns (bool) {
    if (user != onBehalfOf) {
      _decreaseBorrowAllowance(onBehalfOf, user, amount);
    }

    uint256 previousBalance = super.balanceOf(onBehalfOf);
    uint256 amountScaled = amount.rayDiv(index);
    require(amountScaled != 0, Errors.CT_INVALID_MINT_AMOUNT);

    _mint(onBehalfOf, amountScaled);

    emit Transfer(address(0), onBehalfOf, amount);
    emit Mint(user, onBehalfOf, amount, index);

    return previousBalance == 0;
  }

  /**
   * @dev Burns user variable debt
   * - Only callable by the LendingPool
   * @param user The user whose debt is getting burned
   * @param amount The amount getting burned
   * @param index The variable debt index of the reserve
   **/
  function burn(
    address user,
    uint256 amount,
    uint256 index
  ) external override onlyKyokoPool {
    uint256 amountScaled = amount.rayDiv(index);
    require(amountScaled != 0, Errors.CT_INVALID_BURN_AMOUNT);

    _burn(user, amountScaled);

    emit Transfer(user, address(0), amount);
    emit Burn(user, amount, index);
  }

  /**
   * @dev Returns the principal debt balance of the user from
   * @return The debt balance of the user since the last burn/mint action
   **/
  function scaledBalanceOf(address user) public view virtual override returns (uint256) {
    return super.balanceOf(user);
  }

  /**
   * @dev Returns the total supply of the variable debt token. Represents the total debt accrued by the users
   * @return The total supply
   **/
  function totalSupply() public view virtual override returns (uint256) {
    IKyokoPool pool = _getKyokoPool();
    return super.totalSupply().rayMul(pool.getReserveNormalizedVariableDebt(_reserveId));
  }

  /**
   * @dev Returns the scaled total supply of the variable debt token. Represents sum(debt/index)
   * @return the scaled total supply
   **/
  function scaledTotalSupply() public view virtual override returns (uint256) {
    return super.totalSupply();
  }

  /**
   * @dev Returns the principal balance of the user and principal total supply.
   * @param user The address of the user
   * @return The principal balance of the user
   * @return The principal total supply
   **/
  function getScaledUserBalanceAndSupply(address user)
    external
    view
    override
    returns (uint256, uint256)
  {
    return (super.balanceOf(user), super.totalSupply());
  }

  /**
   * @dev Returns the address of the underlying asset of this kToken (E.g. WETH for aWETH)
   **/
  function UNDERLYING_ASSET_ADDRESS() public view returns (address) {
    return _underlyingAsset;
  }

  /**
   * @dev Returns the address of the lending pool where this kToken is used
   **/
  function POOL() public view returns (IKyokoPool) {
    return _getKyokoPool();
  }

  function _getUnderlyingAssetAddress() internal view override returns (address) {
    return _underlyingAsset;
  }

  function _getKyokoPool() internal view override returns (IKyokoPool) {
    return IKyokoPool(_addressesProvider.getKyokoPool()[0]);
  }

  function _getReserveId() internal view override returns (uint256) {
    return _reserveId;
  }
}