// File: @openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

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



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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

// File: @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

// File: @openzeppelin/contracts/token/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

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



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

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @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
     * ====
     *
     * [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://diligence.consensys.net/posts/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.5.11/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/utils/introspection/IERC165.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: @openzeppelin/contracts/utils/structs/EnumerableSet.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

// File: contracts/ajna/ajna-actions/AjnaProxyActions.sol



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

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { IAjnaPoolUtilsInfo } from "../../interfaces/ajna/IAjnaPoolUtilsInfo.sol";
import { IERC20Pool } from "../interfaces/pool/erc20/IERC20Pool.sol";
import { IPositionManager } from "../interfaces/position/IPositionManager.sol";
import { IRewardsManager } from "../interfaces/rewards/IRewardsManager.sol";

import { IAccountGuard } from "../../interfaces/dpm/IAccountGuard.sol";

import { IWETH } from "../../interfaces/tokens/IWETH.sol";

interface IAjnaProxyActions {
    function positionManager() external view returns (IPositionManager);

    function rewardsManager() external view returns (IRewardsManager);

    function ARC() external view returns (address);
}

contract AjnaProxyActions is IAjnaProxyActions {
    IAjnaPoolUtilsInfo public immutable poolInfoUtils;
    IERC20 public immutable ajnaToken;
    address public immutable WETH;
    address public immutable GUARD;
    address public immutable deployer;
    IAjnaProxyActions public immutable self;
    IPositionManager public positionManager;
    IRewardsManager public rewardsManager;
    address public ARC;

    using SafeERC20 for IERC20;

    constructor(IAjnaPoolUtilsInfo _poolInfoUtils, IERC20 _ajnaToken, address _WETH, address _GUARD) {
        poolInfoUtils = _poolInfoUtils;
        ajnaToken = _ajnaToken;
        WETH = _WETH;
        GUARD = _GUARD;
        self = this;
        deployer = msg.sender;
    }

    function initialize(address _positionManager, address _rewardsManager, address _ARC) external {
        require(msg.sender == deployer, "apa/not-deployer");
        require(
            address(positionManager) == address(0) && address(rewardsManager) == address(0) && ARC == address(0),
            "apa/already-initialized"
        );
        positionManager = IPositionManager(_positionManager);
        rewardsManager = IRewardsManager(_rewardsManager);
        ARC = _ARC;
    }

    /**
     * @dev Emitted once an Operation has completed execution
     * @param name Name of the operation
     **/
    event ProxyActionsOperation(bytes32 indexed name);

    /**
     * @dev Emitted when a new position is created
     * @param proxyAddress The address of the newly created position proxy contract
     * @param protocol The name of the protocol associated with the position
     * @param positionType The type of position being created (e.g. borrow or earn)
     * @param collateralToken The address of the collateral token being used for the position
     * @param debtToken The address of the debt token being used for the position
     **/
    event CreatePosition(
        address indexed proxyAddress,
        string protocol,
        string positionType,
        address collateralToken,
        address debtToken
    );

    function _send(address token, uint256 amount) internal {
        if (token == WETH) {
            IWETH(WETH).withdraw(amount);
            payable(msg.sender).transfer(amount);
        } else {
            IERC20(token).safeTransfer(msg.sender, amount);
        }
    }

    function _pull(address token, uint256 amount) internal {
        if (token == WETH) {
            IWETH(WETH).deposit{ value: amount }();
        } else {
            IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        }
    }

    /**
     *  @notice Mints and empty NFT for the user, NFT is bound to a specific pool.
     *  @param  pool            Address of the Ajana Pool.
     *  @return  tokenId  - id of the minted NFT
     */
    function _mintNft(IERC20Pool pool) internal returns (uint256 tokenId) {
        address _ARC = self.ARC();
        tokenId = self.positionManager().mint(address(pool), address(this), keccak256("ERC20_NON_SUBSET_HASH"));
        if (!IAccountGuard(GUARD).canCall(address(this), _ARC)) {
            IAccountGuard(GUARD).permit(_ARC, address(this), true);
        }
    }

    /**
     *  @notice Redeem bucket from NFT
     *  @param  price         Price of the momorialized bucket
     *  @param  tokenId       Nft ID
     *  @param  pool          Pool address
     */

    function _redeemPosition(uint256 price, uint256 tokenId, address pool) internal {
        uint256 index = convertPriceToIndex(price);
        uint256[] memory indexes = new uint256[](1);
        indexes[0] = index;
        address[] memory addresses = new address[](1);
        addresses[0] = address(self.positionManager());
        IERC20Pool(pool).approveLPTransferors(addresses);
        self.positionManager().redeemPositions(address(pool), tokenId, indexes);
    }

    /**
     *  @notice Memorialize bucket in NFT
     *  @param  price         Price of the momorialized bucket
     *  @param  tokenId       Nft ID
     */
    function _memorializeLiquidity(uint256 price, uint256 tokenId, IERC20Pool pool) internal {
        uint256 index = convertPriceToIndex(price);

        (uint256 lpCount, ) = IERC20Pool(pool).lenderInfo(index, address(this));
        uint256[] memory indexes = new uint256[](1);
        indexes[0] = index;
        uint256[] memory lpCounts = new uint256[](1);
        lpCounts[0] = lpCount;
        IERC20Pool(pool).increaseLPAllowance(address(self.positionManager()), indexes, lpCounts);
        self.positionManager().memorializePositions(address(pool), tokenId, indexes);
        IERC721(address(self.positionManager())).approve(address(self.rewardsManager()), tokenId);
    }

    /**
     *  @notice Move LP from one bucket to another while momorialzied in NFT, requires unstaked NFT
     *  @param  oldPrice      Old price of the momorialized bucket
     *  @param  newPrice      New price of the momorialized bucket
     *  @param  tokenId       Nft ID
     *  @param  pool           Pool address
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function _moveLiquidity(
        uint256 oldPrice,
        uint256 newPrice,
        uint256 tokenId,
        address pool,
        bool revertIfBelowLup
    ) internal {
        uint256 oldIndex = convertPriceToIndex(oldPrice);
        uint256 newIndex = convertPriceToIndex(newPrice);

        self.positionManager().moveLiquidity(pool, tokenId, oldIndex, newIndex, block.timestamp + 1, revertIfBelowLup);
        IERC721(address(self.positionManager())).approve(address(self.rewardsManager()), tokenId);
    }

    /**
     *  @notice Called internally to add an amount of credit at a specified price bucket.
     *  @param  pool         Address of the Ajana Pool.
     *  @param  amount       The maximum amount of quote token to be moved by a lender.
     *  @param  price        The price the bucket to which the quote tokens will be added.
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     *  @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance
     *  @dev 1WBTC = 16,990.23 USDC   translates to: 16990230
     */
    function _supplyQuote(IERC20Pool pool, uint256 amount, uint256 price, bool revertIfBelowLup) internal {
        address debtToken = pool.quoteTokenAddress();
        _pull(debtToken, amount);
        uint256 index = convertPriceToIndex(price);
        IERC20(debtToken).safeApprove(address(pool), amount);
        pool.addQuoteToken(amount * pool.quoteTokenScale(), index, block.timestamp + 1, revertIfBelowLup);
    }

    /**
     *  @notice Called internally to move max amount of credit from a specified price bucket to another specified price bucket.
     *  @param  pool         Address of the Ajana Pool.
     *  @param  oldPrice        The price of the bucket  from which the quote tokens will be removed.
     *  @param  newPrice     The price of the bucket to which the quote tokens will be added.
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function _moveQuote(IERC20Pool pool, uint256 oldPrice, uint256 newPrice, bool revertIfBelowLup) internal {
        uint256 oldIndex = convertPriceToIndex(oldPrice);
        pool.moveQuoteToken(
            type(uint256).max,
            oldIndex,
            convertPriceToIndex(newPrice),
            block.timestamp + 1,
            revertIfBelowLup
        );
    }

    /**
     *  @notice Called internally to remove an amount of credit at a specified price bucket.
     *  @param  pool         Address of the Ajana Pool.
     *  @param  amount       The maximum amount of quote token to be moved by a lender.
     *  @param  price        The price the bucket to which the quote tokens will be added.
     *  @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance
     *  @dev 1WBTC = 16,990.23 USDC   translates to: 16990230
     */
    function _withdrawQuote(IERC20Pool pool, uint256 amount, uint256 price) internal {
        address debtToken = pool.quoteTokenAddress();
        uint256 index = convertPriceToIndex(price);
        uint256 balanceBefore = IERC20(debtToken).balanceOf(address(this));
        if (amount == type(uint256).max) {
            pool.removeQuoteToken(type(uint256).max, index);
        } else {
            pool.removeQuoteToken((amount * pool.quoteTokenScale()), index);
        }
        uint256 withdrawnBalance = IERC20(debtToken).balanceOf(address(this)) - balanceBefore;
        _send(debtToken, withdrawnBalance);
    }

    /**
     * @notice Reclaims collateral from liquidated bucket
     * @param  pool         Address of the Ajana Pool.
     * @param  price        Price of the bucket to redeem.
     */
    function _removeCollateral(IERC20Pool pool, uint256 price) internal {
        address collateralToken = pool.collateralAddress();
        uint256 index = convertPriceToIndex(price);
        uint256 balanceBefore = IERC20(collateralToken).balanceOf(address(this));
        pool.removeCollateral(type(uint256).max, index);
        uint256 withdrawnBalance = IERC20(collateralToken).balanceOf(address(this)) - balanceBefore;
        _send(collateralToken, withdrawnBalance);
    }

    // BORROWER ACTIONS

    /**
     *  @notice Deposit collateral
     *  @param  pool           Pool address
     *  @param  collateralAmount Amount of collateral to deposit
     *  @param  price          Price of the bucket
     */
    function depositCollateral(IERC20Pool pool, uint256 collateralAmount, uint256 price) public payable {
        address collateralToken = pool.collateralAddress();
        _pull(collateralToken, collateralAmount);

        uint256 index = convertPriceToIndex(price);
        IERC20(collateralToken).safeApprove(address(pool), collateralAmount);
        pool.drawDebt(address(this), 0, index, collateralAmount * pool.collateralScale());
        emit ProxyActionsOperation("AjnaDeposit");
    }

    /**
     *  @notice Draw debt
     *  @param  pool           Pool address
     *  @param  debtAmount     Amount of debt to draw
     *  @param  price          Price of the bucket
     */
    function drawDebt(IERC20Pool pool, uint256 debtAmount, uint256 price) public {
        address debtToken = pool.quoteTokenAddress();
        uint256 index = convertPriceToIndex(price);

        pool.drawDebt(address(this), debtAmount * pool.quoteTokenScale(), index, 0);
        _send(debtToken, debtAmount);
        emit ProxyActionsOperation("AjnaBorrow");
    }

    /**
     *  @notice Deposit collateral and draw debt
     *  @param  pool           Pool address
     *  @param  debtAmount     Amount of debt to draw
     *  @param  collateralAmount Amount of collateral to deposit
     *  @param  price          Price of the bucket
     */
    function depositCollateralAndDrawDebt(
        IERC20Pool pool,
        uint256 debtAmount,
        uint256 collateralAmount,
        uint256 price
    ) public {
        address debtToken = pool.quoteTokenAddress();
        address collateralToken = pool.collateralAddress();
        uint256 index = convertPriceToIndex(price);
        _pull(collateralToken, collateralAmount);
        IERC20(collateralToken).safeApprove(address(pool), collateralAmount);
        pool.drawDebt(
            address(this),
            debtAmount * pool.quoteTokenScale(),
            index,
            collateralAmount * pool.collateralScale()
        );
        _send(debtToken, debtAmount);
        emit ProxyActionsOperation("AjnaDepositBorrow");
    }

    /**
     *  @notice Deposit collateral and draw debt
     *  @param  pool           Pool address
     *  @param  debtAmount     Amount of debt to borrow
     *  @param  collateralAmount Amount of collateral to deposit
     *  @param  price          Price of the bucket
     */
    function depositAndDraw(
        IERC20Pool pool,
        uint256 debtAmount,
        uint256 collateralAmount,
        uint256 price
    ) public payable {
        if (debtAmount > 0 && collateralAmount > 0) {
            depositCollateralAndDrawDebt(pool, debtAmount, collateralAmount, price);
        } else if (debtAmount > 0) {
            drawDebt(pool, debtAmount, price);
        } else if (collateralAmount > 0) {
            depositCollateral(pool, collateralAmount, price);
        }
    }

    /**
     *  @notice Repay debt
     *  @param  pool           Pool address
     *  @param  amount         Amount of debt to repay
     *  @param  stamploan      Whether to stamp the loan or not
     */
    function repayDebt(IERC20Pool pool, uint256 amount, bool stamploan) public payable {
        address debtToken = pool.quoteTokenAddress();
        _pull(debtToken, amount);
        IERC20(debtToken).safeApprove(address(pool), amount);
        (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool));
        uint256 balanceBefore = IERC20(debtToken).balanceOf(address(this));
        pool.repayDebt(address(this), amount * pool.quoteTokenScale(), 0, address(this), lupIndex_);
        if (stamploan) {
            pool.stampLoan();
        }
        uint256 repaidAmount = balanceBefore - IERC20(debtToken).balanceOf(address(this));
        uint256 leftoverBalance = amount - repaidAmount;
        if (leftoverBalance > 0) {
            _send(debtToken, leftoverBalance);
        }
        emit ProxyActionsOperation("AjnaRepay");
    }

    /**
     *  @notice Withdraw collateral
     *  @param  pool           Pool address
     *  @param  amount         Amount of collateral to withdraw
     */
    function withdrawCollateral(IERC20Pool pool, uint256 amount) public {
        address collateralToken = pool.collateralAddress();
        (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool));
        pool.repayDebt(address(this), 0, amount * pool.collateralScale(), address(this), lupIndex_);
        _send(collateralToken, amount);
        emit ProxyActionsOperation("AjnaWithdraw");
    }

    /**
     *  @notice Repay debt and withdraw collateral
     *  @param  pool           Pool address
     *  @param  debtAmount         Amount of debt to repay
     *  @param  collateralAmount         Amount of collateral to withdraw
     */
    function repayDebtAndWithdrawCollateral(IERC20Pool pool, uint256 debtAmount, uint256 collateralAmount) public {
        address debtToken = pool.quoteTokenAddress();
        address collateralToken = pool.collateralAddress();
        _pull(debtToken, debtAmount);
        IERC20(debtToken).safeApprove(address(pool), debtAmount);
        (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool));
        uint256 quoteBalanceBefore = IERC20(debtToken).balanceOf(address(this));
        pool.repayDebt(
            address(this),
            debtAmount * pool.quoteTokenScale(),
            collateralAmount * pool.collateralScale(),
            address(this),
            lupIndex_
        );
        _send(collateralToken, collateralAmount);
        uint256 repaidAmount = quoteBalanceBefore - IERC20(debtToken).balanceOf(address(this));
        uint256 quoteLeftoverBalance = debtAmount - repaidAmount;
        if (quoteLeftoverBalance > 0) {
            _send(debtToken, quoteLeftoverBalance);
        }
        emit ProxyActionsOperation("AjnaRepayWithdraw");
    }

    /**
     *  @notice Repay debt and withdraw collateral for msg.sender
     *  @param  pool           Pool address
     *  @param  debtAmount     Amount of debt to repay
     *  @param  collateralAmount Amount of collateral to withdraw
     *  @param  stamploan      Whether to stamp the loan or not
     */
    function repayWithdraw(
        IERC20Pool pool,
        uint256 debtAmount,
        uint256 collateralAmount,
        bool stamploan
    ) external payable {
        if (debtAmount > 0 && collateralAmount > 0) {
            repayDebtAndWithdrawCollateral(pool, debtAmount, collateralAmount);
        } else if (debtAmount > 0) {
            repayDebt(pool, debtAmount, stamploan);
        } else if (collateralAmount > 0) {
            withdrawCollateral(pool, collateralAmount);
        }
    }

    /**
     *  @notice Repay debt and close position for msg.sender
     *  @param  pool           Pool address
     */
    function repayAndClose(IERC20Pool pool) public payable {
        address collateralToken = pool.collateralAddress();
        address debtToken = pool.quoteTokenAddress();

        (uint256 debt, uint256 collateral, ) = poolInfoUtils.borrowerInfo(address(pool), address(this));
        uint256 debtPlusBuffer = ((debt / pool.quoteTokenScale()) + 1) * pool.quoteTokenScale();
        uint256 amountDebt = debtPlusBuffer / pool.quoteTokenScale();
        _pull(debtToken, amountDebt);

        IERC20(debtToken).safeApprove(address(pool), amountDebt);
        (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool));
        pool.repayDebt(address(this), debtPlusBuffer, collateral, address(this), lupIndex_);

        uint256 amountCollateral = collateral / pool.collateralScale();
        _send(collateralToken, amountCollateral);
        emit ProxyActionsOperation("AjnaRepayAndClose");
    }

    /**
     *  @notice Open position for msg.sender
     *  @param  pool           Pool address
     *  @param  debtAmount     Amount of debt to borrow
     *  @param  collateralAmount Amount of collateral to deposit
     *  @param  price          Price of the bucket
     */
    function openPosition(IERC20Pool pool, uint256 debtAmount, uint256 collateralAmount, uint256 price) public payable {
        emit CreatePosition(address(this), "Ajna", "Borrow", pool.collateralAddress(), pool.quoteTokenAddress());
        depositAndDraw(pool, debtAmount, collateralAmount, price);
    }

    /**
     *  @notice Open Earn position for msg.sender
     *  @param  pool           Pool address
     *  @param  depositAmount     Amount of debt to borrow
     *  @param  price          Price of the bucket
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function openEarnPosition(
        IERC20Pool pool,
        uint256 depositAmount,
        uint256 price,
        bool revertIfBelowLup
    ) public payable {
        emit CreatePosition(address(this), "Ajna", "Earn", pool.collateralAddress(), pool.quoteTokenAddress());
        _supplyQuote(pool, depositAmount, price, revertIfBelowLup);
        emit ProxyActionsOperation("AjnaSupplyQuote");
    }

    /**
     *  @notice Open Earn (with NFT) position for msg.sender
     *  @param  pool           Pool address
     *  @param  depositAmount     Amount of debt to borrow
     *  @param  price          Price of the bucket
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function openEarnPositionNft(
        IERC20Pool pool,
        uint256 depositAmount,
        uint256 price,
        bool revertIfBelowLup
    ) public payable {
        emit CreatePosition(address(this), "Ajna", "Earn", pool.collateralAddress(), pool.quoteTokenAddress());
        supplyQuoteMintNftAndStake(pool, depositAmount, price, revertIfBelowLup);
    }

    /**
     *  @notice Called by lenders to add an amount of credit at a specified price bucket.
     *  @param  pool         Address of the Ajana Pool.
     *  @param  amount       The maximum amount of quote token to be moved by a lender.
     *  @param  price        The price the bucket to which the quote tokens will be added.
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     *  @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance
     *  @dev 1WBTC = 16,990.23 USDC   translates to: 16990230
     */
    function supplyQuote(IERC20Pool pool, uint256 amount, uint256 price, bool revertIfBelowLup) public payable {
        _supplyQuote(pool, amount, price, revertIfBelowLup);
        emit ProxyActionsOperation("AjnaSupplyQuote");
    }

    /**
     *  @notice Called by lenders to remove an amount of credit at a specified price bucket.
     *  @param  pool         Address of the Ajana Pool.
     *  @param  amount       The maximum amount of quote token to be moved by a lender.
     *  @param  price        The price the bucket to which the quote tokens will be added.
     *  @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance
     *  @dev 1WBTC = 16,990.23 USDC   translates to: 16990230
     */
    function withdrawQuote(IERC20Pool pool, uint256 amount, uint256 price) public {
        _withdrawQuote(pool, amount, price);
        emit ProxyActionsOperation("AjnaWithdrawQuote");
    }

    /**
     *  @notice Called by lenders to move max amount of credit from a specified price bucket to another specified price bucket.
     *  @param  pool         Address of the Ajana Pool.
     *  @param  oldPrice        The price of the bucket  from which the quote tokens will be removed.
     *  @param  newPrice     The price of the bucket to which the quote tokens will be added.
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function moveQuote(IERC20Pool pool, uint256 oldPrice, uint256 newPrice, bool revertIfBelowLup) public {
        _moveQuote(pool, oldPrice, newPrice, revertIfBelowLup);
        emit ProxyActionsOperation("AjnaMoveQuote");
    }

    /**
     *  @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket,
     *  @notice whilst adding additional amount.
     *  @param  pool            Address of the Ajana Pool.
     *  @param  amountToAdd     The maximum amount of quote token to be moved by a lender.
     *  @param  oldPrice        The price of the bucket  from which the quote tokens will be removed.
     *  @param  newPrice        The price of the bucket to which the quote tokens will be added.
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function supplyAndMoveQuote(
        IERC20Pool pool,
        uint256 amountToAdd,
        uint256 oldPrice,
        uint256 newPrice,
        bool revertIfBelowLup
    ) public payable {
        _supplyQuote(pool, amountToAdd, newPrice, revertIfBelowLup);
        _moveQuote(pool, oldPrice, newPrice, revertIfBelowLup);
        emit ProxyActionsOperation("AjnaSupplyAndMoveQuote");
    }

    /**
     *  @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket,
     *  @notice whilst withdrawing additional amount.
     *  @param  pool            Address of the Ajana Pool.
     *  @param  amountToWithdraw     Amount of quote token to be withdrawn by a lender.
     *  @param  oldPrice        The price of the bucket  from which the quote tokens will be removed.
     *  @param  newPrice        The price of the bucket to which the quote tokens will be added.
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function withdrawAndMoveQuote(
        IERC20Pool pool,
        uint256 amountToWithdraw,
        uint256 oldPrice,
        uint256 newPrice,
        bool revertIfBelowLup
    ) public {
        _withdrawQuote(pool, amountToWithdraw, oldPrice);
        _moveQuote(pool, oldPrice, newPrice, revertIfBelowLup);
        emit ProxyActionsOperation("AjnaWithdrawAndMoveQuote");
    }

    // REWARDS

    /**
     *  @notice Mints and NFT, memorizes the LPs of the user and stakes the NFT.
     *  @param  pool     Address of the Ajana Pool.
     *  @param  price    Price of the LPs to be memoriazed.
     *  @return tokenId  Id of the minted NFT
     */
    function _mintAndStakeNft(IERC20Pool pool, uint256 price) internal returns (uint256 tokenId) {
        tokenId = _mintNft(pool);

        _memorializeLiquidity(price, tokenId, pool);

        self.rewardsManager().stake(tokenId);
    }

    /**
     *  @notice Unstakes NFT and redeems position
     *  @param  tokenId      ID of the NFT to modify
     *  @param  pool         Address of the Ajana Pool.
     *  @param  price        Price of the bucket to redeem.
     *  @param  burn         Whether to burn the NFT or not
     */
    function _unstakeNftAndRedeem(uint256 tokenId, IERC20Pool pool, uint256 price, bool burn) internal {
        address _ARC = self.ARC();
        self.rewardsManager().unstake(tokenId);

        _redeemPosition(price, tokenId, address(pool));

        if (burn) {
            self.positionManager().burn(address(pool), tokenId);
            if (IAccountGuard(GUARD).canCall(address(this), _ARC)) {
                IAccountGuard(GUARD).permit(_ARC, address(this), false);
            }
        }
    }

    /**
     *  @notice Supplies quote token, mints and NFT, memorizes the LPs of the user and stakes the NFT.
     *  @param  pool     Address of the Ajana Pool.
     *  @param  amount   The maximum amount of quote token to be deposited by a lender.
     *  @param  price    Price of the bucket to which the quote tokens will be added.
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     *  @return tokenId  Id of the minted NFT
     */
    function supplyQuoteMintNftAndStake(
        IERC20Pool pool,
        uint256 amount,
        uint256 price,
        bool revertIfBelowLup
    ) public payable returns (uint256 tokenId) {
        _supplyQuote(pool, amount, price, revertIfBelowLup);
        tokenId = _mintAndStakeNft(pool, price);
        emit ProxyActionsOperation("AjnaSupplyQuoteMintNftAndStake");
    }

    /**
     *  @notice Adds quote token to existing position and moves to different bucket
     *  @param  pool          Address of the Ajana Pool.
     *  @param  amountToAdd   The maximum amount of quote token to be deposited by a lender.
     *  @param  oldPrice      Index of the bucket to move from.
     *  @param  newPrice      Index of the bucket to move to.
     *  @param  tokenId       ID of the NFT to modify
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function supplyAndMoveQuoteNft(
        IERC20Pool pool,
        uint256 amountToAdd,
        uint256 oldPrice,
        uint256 newPrice,
        uint256 tokenId,
        bool revertIfBelowLup
    ) public payable {
        self.rewardsManager().unstake(tokenId);

        _moveLiquidity(oldPrice, newPrice, tokenId, address(pool), revertIfBelowLup);
        _supplyQuote(pool, amountToAdd, newPrice, revertIfBelowLup);
        _memorializeLiquidity(newPrice, tokenId, pool);

        self.rewardsManager().stake(tokenId);
        emit ProxyActionsOperation("AjnaSupplyAndMoveQuoteNft");
    }

    /**
     *  @notice Adds quote token to existing NFT position
     *  @param  pool          Address of the Ajana Pool.
     *  @param  amountToAdd   The maximum amount of quote token to be deposited by a lender.
     *  @param  price      Price of the bucket to move from.
     *  @param  tokenId       ID of the NFT to modify
     */
    function supplyQuoteNft(
        IERC20Pool pool,
        uint256 amountToAdd,
        uint256 price,
        uint256 tokenId,
        bool revertIfBelowLup
    ) public payable {
        self.rewardsManager().unstake(tokenId);

        _supplyQuote(pool, amountToAdd, price, revertIfBelowLup);
        _memorializeLiquidity(price, tokenId, pool);

        self.rewardsManager().stake(tokenId);
        emit ProxyActionsOperation("AjnaSupplyQuoteNft");
    }

    /**
     *  @notice Withdraws quote token to existing position and moves to different bucket
     *  @param  pool          Address of the Ajana Pool.
     *  @param  amountToWithdraw   The maximum amount of quote token to be withdrawn by a lender.
     *  @param  oldPrice      Index of the bucket to move from.
     *  @param  newPrice      Index of the bucket to move to.
     *  @param  tokenId       ID of the NFT to modify
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function withdrawAndMoveQuoteNft(
        IERC20Pool pool,
        uint256 amountToWithdraw,
        uint256 oldPrice,
        uint256 newPrice,
        uint256 tokenId,
        bool revertIfBelowLup
    ) public payable {
        self.rewardsManager().unstake(tokenId);

        _moveLiquidity(oldPrice, newPrice, tokenId, address(pool), revertIfBelowLup);
        _redeemPosition(newPrice, tokenId, address(pool));
        _withdrawQuote(pool, amountToWithdraw, newPrice);
        _memorializeLiquidity(newPrice, tokenId, pool);

        self.rewardsManager().stake(tokenId);
        emit ProxyActionsOperation("AjnaWithdrawAndMoveQuoteNft");
    }

    /**
     *  @notice Withdraws quote token from existing NFT position
     *  @param  pool          Address of the Ajana Pool.
     *  @param  amountToWithdraw   The maximum amount of quote token to be withdrawn by a lender.
     *  @param  price      Price of the bucket to withdraw from
     *  @param  tokenId       ID of the NFT to modify
     */
    function withdrawQuoteNft(
        IERC20Pool pool,
        uint256 amountToWithdraw,
        uint256 price,
        uint256 tokenId
    ) public payable {
        self.rewardsManager().unstake(tokenId);

        _redeemPosition(price, tokenId, address(pool));
        _withdrawQuote(pool, amountToWithdraw, price);
        _memorializeLiquidity(price, tokenId, pool);

        self.rewardsManager().stake(tokenId);
        emit ProxyActionsOperation("AjnaWithdrawQuoteNft");
    }

    /**
     *  @notice Called by lenders to move an amount of credit from a specified price bucket to another
     *  @notice specified price bucket using staked NFT.
     *  @param  oldPrice     Index of the bucket to move from.
     *  @param  newPrice     Index of the bucket to move to.
     *  @param  tokenId      ID of the NFT to modify
     *  @param  revertIfBelowLup  Revert if the new price is below the LUP
     */
    function moveQuoteNft(
        IERC20Pool pool,
        uint256 oldPrice,
        uint256 newPrice,
        uint256 tokenId,
        bool revertIfBelowLup
    ) public payable {
        self.rewardsManager().unstake(tokenId);
        _moveLiquidity(oldPrice, newPrice, tokenId, address(pool), revertIfBelowLup);
        self.rewardsManager().stake(tokenId);
        emit ProxyActionsOperation("AjnaMoveQuoteNft");
    }

    /**
     *  @notice Claim staking rewards
     *  @param  pool         Address of the Ajana Pool.
     *  @param  tokenId    TokenId to claim rewards for
     */
    function claimRewardsAndSendToOwner(IERC20Pool pool, uint256 tokenId) public {
        uint256 currentEpoch = IERC20Pool(pool).currentBurnEpoch();
        uint256 minAmount = self.rewardsManager().calculateRewards(tokenId, currentEpoch);
        self.rewardsManager().claimRewards(tokenId, currentEpoch, minAmount);
        ajnaToken.transfer(msg.sender, ajnaToken.balanceOf(address(this)));
    }

    /**
     * @notice Unstakes NFT and withdraws quote token
     * @param  pool         Address of the Ajana Pool.
     * @param  price        Price of the bucket to redeem.
     * @param  tokenId      ID of the NFT to unstake
     */
    function unstakeNftAndWithdrawQuote(IERC20Pool pool, uint256 price, uint256 tokenId) public {
        _unstakeNftAndRedeem(tokenId, pool, price, true);
        _withdrawQuote(pool, type(uint256).max, price);
        emit ProxyActionsOperation("AjnaUnstakeNftAndWithdrawQuote");
    }

    /**
     * @notice Unstakes NFT and withdraws quote token and reclaims collateral from liquidated bucket
     * @param  pool         Address of the Ajana Pool.
     * @param  price        Price of the bucket to redeem.
     * @param  tokenId      ID of the NFT to unstake
     */
    function unstakeNftAndClaimCollateral(IERC20Pool pool, uint256 price, uint256 tokenId) public {
        _unstakeNftAndRedeem(tokenId, pool, price, true);
        _removeCollateral(pool, price);
        emit ProxyActionsOperation("AjnaUnstakeNftAndClaimCollateral");
    }

    /**
     * @notice Reclaims collateral from liquidated bucket
     * @param  pool         Address of the Ajana Pool.
     * @param  price        Price of the bucket to redeem.
     */
    function removeCollateral(IERC20Pool pool, uint256 price) public {
        _removeCollateral(pool, price);
        emit ProxyActionsOperation("AjnaRemoveCollateral");
    }

    // OPT IN AND OUT

    /**
     *  @notice Mints and NFT, memorizes the LPs of the user and stakes the NFT.
     *  @param  pool     Address of the Ajana Pool.
     *  @param  price    Price of the LPs to be memoriazed.
     *  @return tokenId  Id of the minted NFT
     */
    function optInStaking(IERC20Pool pool, uint256 price) public returns (uint256 tokenId) {
        tokenId = _mintAndStakeNft(pool, price);
        emit ProxyActionsOperation("AjnaOptInStaking");
    }

    /**
     * @notice Unstakes the NFT, burns it and redeems invested LP tokens, memorized by the user.
     * @param pool Address of the Ajana Pool.
     * @param tokenId Id of the NFT to unstake and burn.
     * @param price Price of the LPs to be redeemed.
     * @dev This function unstakes the NFT which was previously staked and also calls "_unstakeNftAndRedeem" to redeem invested LP tokens.
     */
    function optOutStaking(IERC20Pool pool, uint256 tokenId, uint256 price) public {
        _unstakeNftAndRedeem(tokenId, pool, price, true);
        emit ProxyActionsOperation("AjnaOptOutStaking");
    }

    // VIEW FUNCTIONS
    /**
     * @notice  Converts price to index
     * @param   price   price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance
     * @return index   index of the bucket
     * @dev     price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance
     * @dev     1WBTC = 16,990.23 USDC   translates to: 16990230000000000000000
     */
    function convertPriceToIndex(uint256 price) public view returns (uint256) {
        return poolInfoUtils.priceToIndex(price);
    }

    /**
     *  @notice Get the amount of quote token deposited to a specific bucket
     *  @param  pool         Address of the Ajana Pool.
     *  @param  price        Price of the bucket to query
     *  @return  quoteAmount Amount of quote token deposited to dpecific bucket
     *  @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance
     *  @dev     1WBTC = 16,990.23 USDC   translates to: 16990230000000000000000
     */
    function getQuoteAmount(IERC20Pool pool, uint256 price) public view returns (uint256 quoteAmount) {
        uint256 index = convertPriceToIndex(price);

        (uint256 lpCount, ) = pool.lenderInfo(index, address(this));
        quoteAmount = poolInfoUtils.lpToQuoteTokens(address(pool), lpCount, index);
    }
}

// File: contracts/ajna/interfaces/pool/commons/IPoolBorrowerActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Borrower Actions
 */
interface IPoolBorrowerActions {

    /**
     *  @notice Called by fully colalteralized borrowers to restamp the `Neutral Price` of the loan (only if loan is fully collateralized and not in auction).
     *          The reason for stamping the neutral price on the loan is to provide some certainty to the borrower as to at what price they can expect to be liquidated.
     *          This action can restamp only the loan of `msg.sender`.
     */
    function stampLoan() external;

}

// File: contracts/ajna/interfaces/pool/commons/IPoolDerivedState.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Derived State
 */
interface IPoolDerivedState {

    /**
     *  @notice Returns the exchange rate for a given bucket index.
     *  @param  index_        The bucket index.
     *  @return exchangeRate_ Exchange rate of the bucket (`WAD` precision).
     */
    function bucketExchangeRate(
        uint256 index_
    ) external view returns (uint256 exchangeRate_);

    /**
     *  @notice Returns the prefix sum of a given bucket.
     *  @param  index_   The bucket index.
     *  @return The deposit up to given index (`WAD` precision).
     */
    function depositUpToIndex(
        uint256 index_
    ) external view returns (uint256);

    /**
     *  @notice Returns the bucket index for a given debt amount.
     *  @param  debt_  The debt amount to calculate bucket index for (`WAD` precision).
     *  @return Bucket index.
     */
    function depositIndex(
        uint256 debt_
    ) external view returns (uint256);

    /**
     *  @notice Returns the total amount of quote tokens deposited in pool.
     *  @return Total amount of deposited quote tokens (`WAD` precision).
     */
    function depositSize() external view returns (uint256);

    /**
     *  @notice Returns the meaningful actual utilization of the pool.
     *  @return Deposit utilization (`WAD` precision).
     */
    function depositUtilization() external view returns (uint256);

    /**
     *  @notice Returns the scaling value of deposit at given index.
     *  @param  index_  Deposit index.
     *  @return Deposit scaling (`WAD` precision).
     */
    function depositScale(
        uint256 index_
    ) external view returns (uint256);

}

// File: contracts/ajna/interfaces/pool/commons/IPoolErrors.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Errors.
 */
interface IPoolErrors {
    /**************************/
    /*** Common Pool Errors ***/
    /**************************/

    /**
     *  @notice `LP` allowance is already set by the owner.
     */
    error AllowanceAlreadySet();

    /**
     *  @notice The action cannot be executed on an active auction.
     */
    error AuctionActive();

    /**
     *  @notice Attempted auction to clear doesn't meet conditions.
     */
    error AuctionNotClearable();

    /**
     *  @notice Head auction should be cleared prior of executing this action.
     */
    error AuctionNotCleared();

    /**
     *  @notice The auction price is greater than the arbed bucket price.
     */
    error AuctionPriceGtBucketPrice();

    /**
     *  @notice Pool already initialized.
     */
    error AlreadyInitialized();

    /**
     *  @notice Borrower is attempting to create or modify a loan such that their loan's quote token would be less than the pool's minimum debt amount.
     */
    error AmountLTMinDebt();

    /**
     *  @notice Recipient of borrowed quote tokens doesn't match the caller of the `drawDebt` function.
     */
    error BorrowerNotSender();

    /**
     *  @notice Borrower has a healthy over-collateralized position.
     */
    error BorrowerOk();

    /**
     *  @notice Borrower is attempting to borrow more quote token than they have collateral for.
     */
    error BorrowerUnderCollateralized();

    /**
     *  @notice Operation cannot be executed in the same block when bucket becomes insolvent.
     */
    error BucketBankruptcyBlock();

    /**
     *  @notice User attempted to merge collateral from a lower price bucket into a higher price bucket.
     */
    error CannotMergeToHigherPrice();

    /**
     *  @notice User attempted an operation which does not exceed the dust amount, or leaves behind less than the dust amount.
     */
    error DustAmountNotExceeded();

    /**
     *  @notice Callback invoked by `flashLoan` function did not return the expected hash (see `ERC-3156` spec).
     */
    error FlashloanCallbackFailed();

    /**
     *  @notice Balance of pool contract before flashloan is different than the balance after flashloan.
     */
    error FlashloanIncorrectBalance();

    /**
     *  @notice Pool cannot facilitate a flashloan for the specified token address.
     */
    error FlashloanUnavailableForToken();

    /**
     *  @notice User is attempting to move or pull more collateral than is available.
     */
    error InsufficientCollateral();

    /**
     *  @notice Lender is attempting to move or remove more collateral they have claim to in the bucket.
     *  @notice Lender is attempting to remove more collateral they have claim to in the bucket.
     *  @notice Lender must have enough `LP` to claim the desired amount of quote from the bucket.
     */
    error InsufficientLP();

    /**
     *  @notice Bucket must have more quote available in the bucket than the lender is attempting to claim.
     */
    error InsufficientLiquidity();

    /**
     *  @notice When increasing / decreasing `LP` allowances indexes and amounts arrays parameters should have same length.
     */
    error InvalidAllowancesInput();

    /**
     *  @notice When transferring `LP` between indices, the new index must be a valid index.
     */
    error InvalidIndex();

    /**
     *  @notice The amount used for performed action should be greater than `0`.
     */
    error InvalidAmount();

    /**
     *  @notice Borrower is attempting to borrow more quote token than is available before the supplied `limitIndex`.
     */
    error LimitIndexExceeded();

    /**
     *  @notice When moving quote token `HTP` must stay below `LUP`.
     *  @notice When removing quote token `HTP` must stay below `LUP`.
     */
    error LUPBelowHTP();

    /**
     *  @notice Liquidation must result in `LUP` below the borrowers threshold price.
     */
    error LUPGreaterThanTP();

    /**
     *  @notice From index and to index arguments to move are the same.
     */
    error MoveToSameIndex();

    /**
     *  @notice Owner of the `LP` must have approved the new owner prior to transfer.
     */
    error NoAllowance();

    /**
     *  @notice Actor is attempting to take or clear an inactive auction.
     */
    error NoAuction();

    /**
     *  @notice No pool reserves are claimable.
     */
    error NoReserves();

    /**
     *  @notice Actor is attempting to take or clear an inactive reserves auction.
     */
    error NoReservesAuction();

    /**
     *  @notice Lender must have non-zero `LP` when attemptign to remove quote token from the pool.
     */
    error NoClaim();

    /**
     *  @notice Borrower has no debt to liquidate.
     *  @notice Borrower is attempting to repay when they have no outstanding debt.
     */
    error NoDebt();

    /**
     *  @notice Borrower is attempting to borrow an amount of quote tokens that will push the pool into under-collateralization.
     */
    error PoolUnderCollateralized();

    /**
     *  @notice Actor is attempting to add or move quote tokens at a price below the `LUP`.
     *  @notice Actor is attempting to kick with bucket price below the `LUP`.
     */
    error PriceBelowLUP();

    /**
     *  @notice Lender is attempting to remove quote tokens from a bucket that exists above active auction debt from top-of-book downward.
     */
    error RemoveDepositLockedByAuctionDebt();

    /**
     * @notice User attempted to kick off a new auction less than `2` weeks since the last auction completed.
     */
    error ReserveAuctionTooSoon();

    /**
     *  @notice Take was called before `1` hour had passed from kick time.
     */
    error TakeNotPastCooldown();

    /**
     *  @notice Current block timestamp has reached or exceeded a user-provided expiration.
     */
    error TransactionExpired();

    /**
     *  @notice The address that transfer `LP` is not approved by the `LP` receiving address.
     */
    error TransferorNotApproved();

    /**
     *  @notice Owner of the `LP` attemps to transfer `LP` to same address.
     */
    error TransferToSameOwner();

    /**
     *  @notice The threshold price of the loan to be inserted in loans heap is zero.
     */
    error ZeroThresholdPrice();

}

// File: contracts/ajna/interfaces/pool/commons/IPoolEvents.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Events
 */
interface IPoolEvents {

    /*********************/
    /*** Lender events ***/
    /*********************/

    /**
     *  @notice Emitted when lender adds quote token to the pool.
     *  @param  lender    Recipient that added quote tokens.
     *  @param  index     Index at which quote tokens were added.
     *  @param  amount    Amount of quote tokens added to the pool (`WAD` precision).
     *  @param  lpAwarded Amount of `LP` awarded for the deposit (`WAD` precision).
     *  @param  lup       `LUP` calculated after deposit.
     */
    event AddQuoteToken(
        address indexed lender,
        uint256 indexed index,
        uint256 amount,
        uint256 lpAwarded,
        uint256 lup
    );

    /**
     *  @notice Emitted when lender moves quote token from a bucket price to another.
     *  @param  lender         Recipient that moved quote tokens.
     *  @param  from           Price bucket from which quote tokens were moved.
     *  @param  to             Price bucket where quote tokens were moved.
     *  @param  amount         Amount of quote tokens moved (`WAD` precision).
     *  @param  lpRedeemedFrom Amount of `LP` removed from the `from` bucket (`WAD` precision).
     *  @param  lpAwardedTo    Amount of `LP` credited to the `to` bucket (`WAD` precision).
     *  @param  lup            `LUP` calculated after removal.
     */
    event MoveQuoteToken(
        address indexed lender,
        uint256 indexed from,
        uint256 indexed to,
        uint256 amount,
        uint256 lpRedeemedFrom,
        uint256 lpAwardedTo,
        uint256 lup
    );

    /**
     *  @notice Emitted when lender removes quote token from the pool.
     *  @param  lender     Recipient that removed quote tokens.
     *  @param  index      Index at which quote tokens were removed.
     *  @param  amount     Amount of quote tokens removed from the pool (`WAD` precision).
     *  @param  lpRedeemed Amount of `LP` exchanged for quote token (`WAD` precision).
     *  @param  lup        `LUP` calculated after removal.
     */
    event RemoveQuoteToken(
        address indexed lender,
        uint256 indexed index,
        uint256 amount,
        uint256 lpRedeemed,
        uint256 lup
    );

    /**
     *  @notice Emitted when lender claims collateral from a bucket.
     *  @param  claimer    Recipient that claimed collateral.
     *  @param  index      Index at which collateral was claimed.
     *  @param  amount     The amount of collateral (`WAD` precision for `ERC20` pools, number of `NFT` tokens for `ERC721` pools) transferred to the claimer.
     *  @param  lpRedeemed Amount of `LP` exchanged for quote token (`WAD` precision).
     */
    event RemoveCollateral(
        address indexed claimer,
        uint256 indexed index,
        uint256 amount,
        uint256 lpRedeemed
    );

    /***********************/
    /*** Borrower events ***/
    /***********************/

    /**
     *  @notice Emitted when borrower repays quote tokens to the pool and/or pulls collateral from the pool.
     *  @param  borrower         `msg.sender` or on behalf of sender.
     *  @param  quoteRepaid      Amount of quote tokens repaid to the pool (`WAD` precision).
     *  @param  collateralPulled The amount of collateral (`WAD` precision for `ERC20` pools, number of `NFT` tokens for `ERC721` pools) transferred to the claimer.
     *  @param  lup              `LUP` after repay.
     */
    event RepayDebt(
        address indexed borrower,
        uint256 quoteRepaid,
        uint256 collateralPulled,
        uint256 lup
    );

    /**********************/
    /*** Auction events ***/
    /**********************/

    /**
     *  @notice Emitted when a liquidation is initiated.
     *  @param  borrower   Identifies the loan being liquidated.
     *  @param  debt       Debt the liquidation will attempt to cover (`WAD` precision).
     *  @param  collateral Amount of collateral up for liquidation (`WAD` precision for `ERC20` pools, number of `NFT` tokens for `ERC721` pools).
     *  @param  bond       Bond amount locked by kicker (`WAD` precision).
     */
    event Kick(
        address indexed borrower,
        uint256 debt,
        uint256 collateral,
        uint256 bond
    );

    /**
     *  @notice Emitted when kickers are withdrawing funds posted as auction bonds.
     *  @param  kicker   The kicker withdrawing bonds.
     *  @param  reciever The address receiving withdrawn bond amount.
     *  @param  amount   The bond amount that was withdrawn (`WAD` precision).
     */
    event BondWithdrawn(
        address indexed kicker,
        address indexed reciever,
        uint256 amount
    );

    /**
     *  @notice Emitted when an actor uses quote token to arb higher-priced deposit off the book.
     *  @param  borrower    Identifies the loan being liquidated.
     *  @param  index       The index of the `Highest Price Bucket` used for this take.
     *  @param  amount      Amount of quote token used to purchase collateral (`WAD` precision).
     *  @param  collateral  Amount of collateral purchased with quote token (`WAD` precision).
     *  @param  bondChange  Impact of this take to the liquidation bond (`WAD` precision).
     *  @param  isReward    `True` if kicker was rewarded with `bondChange` amount, `false` if kicker was penalized.
     *  @dev    amount / collateral implies the auction price.
     */
    event BucketTake(
        address indexed borrower,
        uint256 index,
        uint256 amount,
        uint256 collateral,
        uint256 bondChange,
        bool    isReward
    );

    /**
     *  @notice Emitted when `LP` are awarded to a taker or kicker in a bucket take.
     *  @param  taker           Actor who invoked the bucket take.
     *  @param  kicker          Actor who started the auction.
     *  @param  lpAwardedTaker  Amount of `LP` awarded to the taker (`WAD` precision).
     *  @param  lpAwardedKicker Amount of `LP` awarded to the actor who started the auction (`WAD` precision).
     */
    event BucketTakeLPAwarded(
        address indexed taker,
        address indexed kicker,
        uint256 lpAwardedTaker,
        uint256 lpAwardedKicker
    );

    /**
     *  @notice Emitted when an actor uses quote token outside of the book to purchase collateral under liquidation.
     *  @param  borrower   Identifies the loan being liquidated.
     *  @param  amount     Amount of quote token used to purchase collateral (`WAD` precision).
     *  @param  collateral Amount of collateral purchased with quote token (for `ERC20` pool, `WAD` precision) or number of `NFT`s purchased (for `ERC721` pool).
     *  @param  bondChange Impact of this take to the liquidation bond (`WAD` precision).
     *  @param  isReward   `True` if kicker was rewarded with `bondChange` amount, `false` if kicker was penalized.
     *  @dev    amount / collateral implies the auction price.
     */
    event Take(
        address indexed borrower,
        uint256 amount,
        uint256 collateral,
        uint256 bondChange,
        bool    isReward
    );

    /**
     *  @notice Emitted when an actor settles debt in a completed liquidation
     *  @param  borrower    Identifies the loan under liquidation.
     *  @param  settledDebt Amount of pool debt settled in this transaction (`WAD` precision).
     *  @dev    When `amountRemaining_ == 0`, the auction has been completed cleared and removed from the queue.
     */
    event Settle(
        address indexed borrower,
        uint256 settledDebt
    );

    /**
     *  @notice Emitted when auction is completed.
     *  @param  borrower   Address of borrower that exits auction.
     *  @param  collateral Borrower's remaining collateral when auction completed (`WAD` precision).
     */
    event AuctionSettle(
        address indexed borrower,
        uint256 collateral
    );

    /**
     *  @notice Emitted when `NFT` auction is completed.
     *  @param  borrower   Address of borrower that exits auction.
     *  @param  collateral Borrower's remaining collateral when auction completed.
     *  @param  lp         Amount of `LP` given to the borrower to compensate fractional collateral (if any, `WAD` precision).
     *  @param  index      Index of the bucket with `LP` to compensate fractional collateral.
     */
    event AuctionNFTSettle(
        address indexed borrower,
        uint256 collateral,
        uint256 lp,
        uint256 index
    );

    /**
     *  @notice Emitted when a `Claimaible Reserve Auction` is started.
     *  @param  claimableReservesRemaining Amount of claimable reserves which has not yet been taken (`WAD` precision).
     *  @param  auctionPrice               Current price at which `1` quote token may be purchased, denominated in `Ajna`.
     *  @param  currentBurnEpoch           Current burn epoch.
     */
    event KickReserveAuction(
        uint256 claimableReservesRemaining,
        uint256 auctionPrice,
        uint256 currentBurnEpoch
    );

    /**
     *  @notice Emitted when a `Claimaible Reserve Auction` is taken.
     *  @param  claimableReservesRemaining Amount of claimable reserves which has not yet been taken (`WAD` precision).
     *  @param  auctionPrice               Current price at which `1` quote token may be purchased, denominated in `Ajna`.
     *  @param  currentBurnEpoch           Current burn epoch.
     */
    event ReserveAuction(
        uint256 claimableReservesRemaining,
        uint256 auctionPrice,
        uint256 currentBurnEpoch
    );

    /**************************/
    /*** LP transfer events ***/
    /**************************/

    /**
     *  @notice Emitted when owner increase the `LP` allowance of a spender at specified indexes with specified amounts.
     *  @param  owner     `LP` owner.
     *  @param  spender   Address approved to transfer `LP`.
     *  @param  indexes   Bucket indexes of `LP` approved.
     *  @param  amounts   `LP` amounts added (ordered by indexes, `WAD` precision).
     */
    event IncreaseLPAllowance(
        address indexed owner,
        address indexed spender,
        uint256[] indexes,
        uint256[] amounts
    );

    /**
     *  @notice Emitted when owner decrease the `LP` allowance of a spender at specified indexes with specified amounts.
     *  @param  owner     `LP` owner.
     *  @param  spender   Address approved to transfer `LP`.
     *  @param  indexes   Bucket indexes of `LP` approved.
     *  @param  amounts   `LP` amounts removed (ordered by indexes, `WAD` precision).
     */
    event DecreaseLPAllowance(
        address indexed owner,
        address indexed spender,
        uint256[] indexes,
        uint256[] amounts
    );

    /**
     *  @notice Emitted when lender removes the allowance of a spender for their `LP`.
     *  @param  owner   `LP` owner.
     *  @param  spender Address that is having it's allowance revoked.
     *  @param  indexes List of bucket index to remove the allowance from.
     */
    event RevokeLPAllowance(
        address indexed owner,
        address indexed spender,
        uint256[] indexes
    );

    /**
     *  @notice Emitted when lender whitelists addresses to accept `LP` from.
     *  @param  lender      Recipient that approves new owner for `LP`.
     *  @param  transferors List of addresses that can transfer `LP` to lender.
     */
    event ApproveLPTransferors(
        address indexed lender,
        address[] transferors
    );

    /**
     *  @notice Emitted when lender removes addresses from the `LP` transferors whitelist.
     *  @param  lender      Recipient that approves new owner for `LP`.
     *  @param  transferors List of addresses that won't be able to transfer `LP` to lender anymore.
     */
    event RevokeLPTransferors(
        address indexed lender,
        address[] transferors
    );

    /**
     *  @notice Emitted when a lender transfers their `LP` to a different address.
     *  @dev    Used by `PositionManager.memorializePositions()`.
     *  @param  owner    The original owner address of the position.
     *  @param  newOwner The new owner address of the position.
     *  @param  indexes  Array of price bucket indexes at which `LP` were transferred.
     *  @param  lp       Amount of `LP` transferred (`WAD` precision).
     */
    event TransferLP(
        address owner,
        address newOwner,
        uint256[] indexes,
        uint256 lp
    );

    /**************************/
    /*** Pool common events ***/
    /**************************/

    /**
     *  @notice Emitted when `LP` are forfeited as a result of the bucket losing all assets.
     *  @param  index       The index of the bucket.
     *  @param  lpForfeited Amount of `LP` forfeited by lenders (`WAD` precision).
     */
    event BucketBankruptcy(
        uint256 indexed index,
        uint256 lpForfeited
    );

    /**
     *  @notice Emitted when a flashloan is taken from pool.
     *  @param  receiver The address receiving the flashloan.
     *  @param  token    The address of token flashloaned from pool.
     *  @param  amount   The amount of tokens flashloaned from pool (token precision).
     */
    event Flashloan(
        address indexed receiver,
        address indexed token,
        uint256 amount
    );

    /**
     *  @notice Emitted when a loan `Neutral Price` is restamped.
     *  @param  borrower Identifies the loan to update the `Neutral Price`.
     */
    event LoanStamped(
        address indexed borrower
    );

    /**
     *  @notice Emitted when pool interest rate is reset. This happens when `interest rate > 10%` and `debtEma < 5%` of `depositEma`
     *  @param  oldRate Old pool interest rate.
     *  @param  newRate New pool interest rate.
     */
    event ResetInterestRate(
        uint256 oldRate,
        uint256 newRate
    );

    /**
     *  @notice Emitted when pool interest rate is updated.
     *  @param  oldRate Old pool interest rate.
     *  @param  newRate New pool interest rate.
     */
    event UpdateInterestRate(
        uint256 oldRate,
        uint256 newRate
    );

}
// File: contracts/ajna/interfaces/pool/commons/IPoolImmutables.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Immutables
 */
interface IPoolImmutables {

    /**
     *  @notice Returns the type of the pool (`0` for `ERC20`, `1` for `ERC721`).
     */
    function poolType() external pure returns (uint8);

    /**
     *  @notice Returns the address of the pool's collateral token.
     */
    function collateralAddress() external pure returns (address);

    /**
     *  @notice Returns the address of the pool's quote token.
     */
    function quoteTokenAddress() external pure returns (address);

    /**
     *  @notice Returns the `quoteTokenScale` state variable.
     *  @notice Token scale is also the minimum amount a lender may have in a bucket (dust amount).
     *  @return The precision of the quote `ERC20` token based on decimals.
     */
    function quoteTokenScale() external pure returns (uint256);
}
// File: contracts/ajna/interfaces/pool/commons/IPoolKickerActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Kicker Actions
 */
interface IPoolKickerActions {

    /********************/
    /*** Liquidations ***/
    /********************/

    /**
     *  @notice Called by actors to initiate a liquidation.
     *  @param  borrower_     Identifies the loan to liquidate.
     *  @param  npLimitIndex_ Index of the lower bound of `NP` tolerated when kicking the auction.
     */
    function kick(
        address borrower_,
        uint256 npLimitIndex_
    ) external;

    /**
     *  @notice Called by lenders to liquidate the top loan.
     *  @param  index_        The deposit index to use for kicking the top loan.
     *  @param  npLimitIndex_ Index of the lower bound of `NP` tolerated when kicking the auction.
     */
    function lenderKick(
        uint256 index_,
        uint256 npLimitIndex_
    ) external;

    /**
     *  @notice Called by kickers to withdraw their auction bonds (the amount of quote tokens that are not locked in active auctions).
     *  @param  recipient_ Address to receive claimed bonds amount.
     *  @param  maxAmount_ The max amount to withdraw from auction bonds (`WAD` precision). Constrained by claimable amounts and liquidity.
     */
    function withdrawBonds(
        address recipient_,
        uint256 maxAmount_
    ) external;

    /***********************/
    /*** Reserve Auction ***/
    /***********************/

    /**
     *  @notice Called by actor to start a `Claimable Reserve Auction` (`CRA`).
     */
    function kickReserveAuction() external;
}
// File: contracts/ajna/interfaces/pool/commons/IPoolLenderActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Lender Actions
 */
interface IPoolLenderActions {

    /*********************************************/
    /*** Quote/collateral management functions ***/
    /*********************************************/

    /**
     *  @notice Called by lenders to add an amount of credit at a specified price bucket.
     *  @param  amount_           The amount of quote token to be added by a lender (`WAD` precision).
     *  @param  index_            The index of the bucket to which the quote tokens will be added.
     *  @param  expiry_           Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price.
     *  @param  revertIfBelowLup_ The tx will revert if price of the bucket to which the quote tokens will be added is below `LUP` price (and avoid paying fee for deposit below `LUP`).
     *  @return bucketLP_         The amount of `LP` changed for the added quote tokens (`WAD` precision).
     */
    function addQuoteToken(
        uint256 amount_,
        uint256 index_,
        uint256 expiry_,
        bool    revertIfBelowLup_
    ) external returns (uint256 bucketLP_);

    /**
     *  @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket.
     *  @param  maxAmount_        The maximum amount of quote token to be moved by a lender (`WAD` precision).
     *  @param  fromIndex_        The bucket index from which the quote tokens will be removed.
     *  @param  toIndex_          The bucket index to which the quote tokens will be added.
     *  @param  expiry_           Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price.
     *  @param  revertIfBelowLup_ The tx will revert if quote token is moved from above the `LUP` to below the `LUP` (and avoid paying fee for move below `LUP`).
     *  @return fromBucketLP_     The amount of `LP` moved out from bucket (`WAD` precision).
     *  @return toBucketLP_       The amount of `LP` moved to destination bucket (`WAD` precision).
     *  @return movedAmount_      The amount of quote token moved (`WAD` precision).
     */
    function moveQuoteToken(
        uint256 maxAmount_,
        uint256 fromIndex_,
        uint256 toIndex_,
        uint256 expiry_,
        bool    revertIfBelowLup_
    ) external returns (uint256 fromBucketLP_, uint256 toBucketLP_, uint256 movedAmount_);

    /**
     *  @notice Called by lenders to claim collateral from a price bucket.
     *  @param  maxAmount_     The amount of collateral (`WAD` precision for `ERC20` pools, number of `NFT` tokens for `ERC721` pools) to claim.
     *  @param  index_         The bucket index from which collateral will be removed.
     *  @return removedAmount_ The amount of collateral removed (`WAD` precision).
     *  @return redeemedLP_    The amount of `LP` used for removing collateral amount (`WAD` precision).
     */
    function removeCollateral(
        uint256 maxAmount_,
        uint256 index_
    ) external returns (uint256 removedAmount_, uint256 redeemedLP_);

    /**
     *  @notice Called by lenders to remove an amount of credit at a specified price bucket.
     *  @param  maxAmount_     The max amount of quote token to be removed by a lender (`WAD` precision).
     *  @param  index_         The bucket index from which quote tokens will be removed.
     *  @return removedAmount_ The amount of quote token removed (`WAD` precision).
     *  @return redeemedLP_    The amount of `LP` used for removing quote tokens amount (`WAD` precision).
     */
    function removeQuoteToken(
        uint256 maxAmount_,
        uint256 index_
    ) external returns (uint256 removedAmount_, uint256 redeemedLP_);

    /********************************/
    /*** Interest update function ***/
    /********************************/

    /**
     *  @notice Called by actors to update pool interest rate (can be updated only once in a `12` hours period of time).
     */
    function updateInterest() external;

}

// File: contracts/ajna/interfaces/pool/commons/IPoolLPActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool `LP` Actions
 */
interface IPoolLPActions {

    /**
     *  @notice Called by `LP` owners to approve transfer of an amount of `LP` to a new owner.
     *  @dev    Intended for use by the `PositionManager` contract.
     *  @param  spender_ The new owner of the `LP`.
     *  @param  indexes_ Bucket indexes from where `LP` are transferred.
     *  @param  amounts_ The amounts of `LP` approved to transfer (`WAD` precision).
     */
    function increaseLPAllowance(
        address spender_,
        uint256[] calldata indexes_,
        uint256[] calldata amounts_
    ) external;

    /**
     *  @notice Called by `LP` owners to decrease the amount of `LP` that can be spend by a new owner.
     *  @dev    Intended for use by the `PositionManager` contract.
     *  @param  spender_ The new owner of the `LP`.
     *  @param  indexes_ Bucket indexes from where `LP` are transferred.
     *  @param  amounts_ The amounts of `LP` disapproved to transfer (`WAD` precision).
     */
    function decreaseLPAllowance(
        address spender_,
        uint256[] calldata indexes_,
        uint256[] calldata amounts_
    ) external;

    /**
     *  @notice Called by `LP` owners to decrease the amount of `LP` that can be spend by a new owner.
     *  @param  spender_ Address that is having it's allowance revoked.
     *  @param  indexes_ List of bucket index to remove the allowance from.
     */
    function revokeLPAllowance(
        address spender_,
        uint256[] calldata indexes_
    ) external;

    /**
     *  @notice Called by `LP` owners to allow addresses that can transfer LP.
     *  @dev    Intended for use by the `PositionManager` contract.
     *  @param  transferors_ Addresses that are allowed to transfer `LP` to new owner.
     */
    function approveLPTransferors(
        address[] calldata transferors_
    ) external;

    /**
     *  @notice Called by `LP` owners to revoke addresses that can transfer `LP`.
     *  @dev    Intended for use by the `PositionManager` contract.
     *  @param  transferors_ Addresses that are revoked to transfer `LP` to new owner.
     */
    function revokeLPTransferors(
        address[] calldata transferors_
    ) external;

    /**
     *  @notice Called by `LP` owners to transfers their `LP` to a different address. `approveLpOwnership` needs to be run first.
     *  @dev    Used by `PositionManager.memorializePositions()`.
     *  @param  owner_    The original owner address of the position.
     *  @param  newOwner_ The new owner address of the position.
     *  @param  indexes_  Array of price buckets index at which `LP` were moved.
     */
    function transferLP(
        address owner_,
        address newOwner_,
        uint256[] calldata indexes_
    ) external;
}

// File: contracts/ajna/interfaces/pool/commons/IPoolSettlerActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Settler Actions
 */
interface IPoolSettlerActions {

    /**
     *  @notice Called by actors to settle an amount of debt in a completed liquidation.
     *  @param  borrowerAddress_ Address of the auctioned borrower.
     *  @param  maxDepth_        Measured from `HPB`, maximum number of buckets deep to settle debt.
     *  @dev    `maxDepth_` is used to prevent unbounded iteration clearing large liquidations.
     */
    function settle(
        address borrowerAddress_,
        uint256 maxDepth_
    ) external;

}
// File: contracts/ajna/interfaces/pool/commons/IPoolState.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool State
 */
interface IPoolState {

    /**
     *  @notice Returns details of an auction for a given borrower address.
     *  @param  borrower_     Address of the borrower that is liquidated.
     *  @return kicker_       Address of the kicker that is kicking the auction.
     *  @return bondFactor_   The factor used for calculating bond size.
     *  @return bondSize_     The bond amount in quote token terms.
     *  @return kickTime_     Time the liquidation was initiated.
     *  @return kickMomp_     Price where the average loan utilizes deposit, at the time when the loan is liquidated (kicked).
     *  @return neutralPrice_ `Neutral Price` of auction.
     *  @return head_         Address of the head auction.
     *  @return next_         Address of the next auction in queue.
     *  @return prev_         Address of the prev auction in queue.
     *  @return alreadyTaken_ True if take has been called on auction
     */
    function auctionInfo(address borrower_)
        external
        view
        returns (
            address kicker_,
            uint256 bondFactor_,
            uint256 bondSize_,
            uint256 kickTime_,
            uint256 kickMomp_,
            uint256 neutralPrice_,
            address head_,
            address next_,
            address prev_,
            bool alreadyTaken_
        );

    /**
     *  @notice Returns pool related debt values.
     *  @return debt_                Current amount of debt owed by borrowers in pool.
     *  @return accruedDebt_         Debt owed by borrowers based on last inflator snapshot.
     *  @return debtInAuction_       Total amount of debt in auction.
     *  @return t0Debt2ToCollateral_ t0debt accross all borrowers divided by their collateral, used in determining a collateralization weighted debt.
     */
    function debtInfo()
        external
        view
        returns (
            uint256 debt_,
            uint256 accruedDebt_,
            uint256 debtInAuction_,
            uint256 t0Debt2ToCollateral_
        );

    /**
     *  @notice Mapping of borrower addresses to `Borrower` structs.
     *  @dev    NOTE: Cannot use appended underscore syntax for return params since struct is used.
     *  @param  borrower_   Address of the borrower.
     *  @return t0Debt_     Amount of debt borrower would have had if their loan was the first debt drawn from the pool.
     *  @return collateral_ Amount of collateral that the borrower has deposited, in collateral token.
     *  @return t0Np_       t0 `Neutral Price`
     */
    function borrowerInfo(address borrower_)
        external
        view
        returns (
            uint256 t0Debt_,
            uint256 collateral_,
            uint256 t0Np_
        );

    /**
     *  @notice Mapping of buckets indexes to `Bucket` structs.
     *  @dev    NOTE: Cannot use appended underscore syntax for return params since struct is used.
     *  @param  index_               Bucket index.
     *  @return lpAccumulator_       Amount of `LP` accumulated in current bucket.
     *  @return availableCollateral_ Amount of collateral available in current bucket.
     *  @return bankruptcyTime_      Timestamp when bucket become insolvent, `0` if healthy.
     *  @return bucketDeposit_       Amount of quote tokens in bucket.
     *  @return bucketScale_         Bucket multiplier.
     */
    function bucketInfo(uint256 index_)
        external
        view
        returns (
            uint256 lpAccumulator_,
            uint256 availableCollateral_,
            uint256 bankruptcyTime_,
            uint256 bucketDeposit_,
            uint256 bucketScale_
        );

    /**
     *  @notice Mapping of burnEventEpoch to `BurnEvent` structs.
     *  @dev    Reserve auctions correspond to burn events.
     *  @param  burnEventEpoch_  Id of the current reserve auction.
     *  @return burnBlock_       Block in which a reserve auction started.
     *  @return totalInterest_   Total interest as of the reserve auction.
     *  @return totalBurned_     Total ajna tokens burned as of the reserve auction.
     */
    function burnInfo(uint256 burnEventEpoch_) external view returns (uint256, uint256, uint256);

    /**
     *  @notice Returns the latest `burnEventEpoch` of reserve auctions.
     *  @dev    If a reserve auction is active, it refers to the current reserve auction. If no reserve auction is active, it refers to the last reserve auction.
     *  @return Current `burnEventEpoch`.
     */
    function currentBurnEpoch() external view returns (uint256);

    /**
     *  @notice Returns information about the pool `EMA (Exponential Moving Average)` variables.
     *  @return debtColEma_   Debt squared to collateral Exponential, numerator to `TU` calculation.
     *  @return lupt0DebtEma_ Exponential of `LUP * t0 debt`, denominator to `TU` calculation
     *  @return debtEma_      Exponential debt moving average.
     *  @return depositEma_   sample of meaningful deposit Exponential, denominator to `MAU` calculation.
     */
    function emasInfo()
        external
        view
        returns (
            uint256 debtColEma_,
            uint256 lupt0DebtEma_,
            uint256 debtEma_,
            uint256 depositEma_
    );

    /**
     *  @notice Returns information about pool inflator.
     *  @return inflator_   Pool inflator value.
     *  @return lastUpdate_ The timestamp of the last `inflator` update.
     */
    function inflatorInfo()
        external
        view
        returns (
            uint256 inflator_,
            uint256 lastUpdate_
    );

    /**
     *  @notice Returns information about pool interest rate.
     *  @return interestRate_       Current interest rate in pool.
     *  @return interestRateUpdate_ The timestamp of the last interest rate update.
     */
    function interestRateInfo()
        external
        view
        returns (
            uint256 interestRate_,
            uint256 interestRateUpdate_
        );


    /**
     *  @notice Returns details about kicker balances.
     *  @param  kicker_    The address of the kicker to retrieved info for.
     *  @return claimable_ Amount of quote token kicker can claim / withdraw from pool at any time.
     *  @return locked_    Amount of quote token kicker locked in auctions (as bonds).
     */
    function kickerInfo(address kicker_)
        external
        view
        returns (
            uint256 claimable_,
            uint256 locked_
        );

    /**
     *  @notice Mapping of buckets indexes and owner addresses to `Lender` structs.
     *  @param  index_       Bucket index.
     *  @param  lender_      Address of the liquidity provider.
     *  @return lpBalance_   Amount of `LP` owner has in current bucket.
     *  @return depositTime_ Time the user last deposited quote token.
     */
    function lenderInfo(
        uint256 index_,
        address lender_
    )
        external
        view
        returns (
            uint256 lpBalance_,
            uint256 depositTime_
    );

    /**
     *  @notice Return the `LP` allowance a `LP` owner provided to a spender.
     *  @param  index_     Bucket index.
     *  @param  spender_   Address of the `LP` spender.
     *  @param  owner_     The initial owner of the `LP`.
     *  @return allowance_ Amount of `LP` spender can utilize.
     */
    function lpAllowance(
        uint256 index_,
        address spender_,
        address owner_
    ) external view returns (uint256 allowance_);

    /**
     *  @notice Returns information about a loan in the pool.
     *  @param  loanId_         Loan's id within loan heap. Max loan is position `1`.
     *  @return borrower_       Borrower address at the given position.
     *  @return thresholdPrice_ Borrower threshold price in pool.
     */
    function loanInfo(
        uint256 loanId_
    )
        external
        view
        returns (
            address borrower_,
            uint256 thresholdPrice_
    );

    /**
     *  @notice Returns information about pool loans.
     *  @return maxBorrower_       Borrower address with highest threshold price.
     *  @return maxThresholdPrice_ Highest threshold price in pool.
     *  @return noOfLoans_         Total number of loans.
     */
    function loansInfo()
        external
        view
        returns (
            address maxBorrower_,
            uint256 maxThresholdPrice_,
            uint256 noOfLoans_
    );

    /**
     *  @notice Returns information about pool reserves.
     *  @return liquidationBondEscrowed_ Amount of liquidation bond across all liquidators.
     *  @return reserveAuctionUnclaimed_ Amount of claimable reserves which has not been taken in the `Claimable Reserve Auction`.
     *  @return reserveAuctionKicked_    Time a `Claimable Reserve Auction` was last kicked.
     *  @return totalInterestEarned_     Total interest earned by all lenders in the pool
     */
    function reservesInfo()
        external
        view
        returns (
            uint256 liquidationBondEscrowed_,
            uint256 reserveAuctionUnclaimed_,
            uint256 reserveAuctionKicked_,
            uint256 totalInterestEarned_
    );

    /**
     *  @notice Returns the `pledgedCollateral` state variable.
     *  @return The total pledged collateral in the system, in WAD units.
     */
    function pledgedCollateral() external view returns (uint256);

    /**
     *  @notice Returns the total number of active auctions in pool.
     *  @return totalAuctions_ Number of active auctions.
     */
    function totalAuctionsInPool() external view returns (uint256);

     /**
     *  @notice Returns the `t0Debt` state variable.
     *  @dev    This value should be multiplied by inflator in order to calculate current debt of the pool.
     *  @return The total `t0Debt` in the system, in `WAD` units.
     */
    function totalT0Debt() external view returns (uint256);

    /**
     *  @notice Returns the `t0DebtInAuction` state variable.
     *  @dev    This value should be multiplied by inflator in order to calculate current debt in auction of the pool.
     *  @return The total `t0DebtInAuction` in the system, in `WAD` units.
     */
    function totalT0DebtInAuction() external view returns (uint256);

    /**
     *  @notice Mapping of addresses that can transfer `LP` to a given lender.
     *  @param  lender_     Lender that receives `LP`.
     *  @param  transferor_ Transferor that transfers `LP`.
     *  @return True if the transferor is approved by lender.
     */
    function approvedTransferors(
        address lender_,
        address transferor_
    ) external view returns (bool);

}

/*********************/
/*** State Structs ***/
/*********************/

/******************/
/*** Pool State ***/
/******************/

/// @dev Struct holding inflator state.
struct InflatorState {
    uint208 inflator;       // [WAD] pool's inflator
    uint48  inflatorUpdate; // [SEC] last time pool's inflator was updated
}

/// @dev Struct holding pool interest state.
struct InterestState {
    uint208 interestRate;        // [WAD] pool's interest rate
    uint48  interestRateUpdate;  // [SEC] last time pool's interest rate was updated (not before 12 hours passed)
    uint256 debt;                // [WAD] previous update's debt
    uint256 meaningfulDeposit;   // [WAD] previous update's meaningfulDeposit
    uint256 t0Debt2ToCollateral; // [WAD] utilization weight accumulator, tracks debt and collateral relationship accross borrowers 
    uint256 debtCol;             // [WAD] previous debt squared to collateral
    uint256 lupt0Debt;           // [WAD] previous LUP * t0 debt
}

/// @dev Struct holding pool EMAs state.
struct EmaState {
    uint256 debtEma;             // [WAD] sample of debt EMA, numerator to MAU calculation
    uint256 depositEma;          // [WAD] sample of meaningful deposit EMA, denominator to MAU calculation
    uint256 debtColEma;          // [WAD] debt squared to collateral EMA, numerator to TU calculation
    uint256 lupt0DebtEma;        // [WAD] EMA of LUP * t0 debt, denominator to TU calculation
    uint256 emaUpdate;           // [SEC] last time pool's EMAs were updated
}

/// @dev Struct holding pool balances state.
struct PoolBalancesState {
    uint256 pledgedCollateral; // [WAD] total collateral pledged in pool
    uint256 t0DebtInAuction;   // [WAD] Total debt in auction used to restrict LPB holder from withdrawing
    uint256 t0Debt;            // [WAD] Pool debt as if the whole amount was incurred upon the first loan
}

/// @dev Struct holding pool params (in memory only).
struct PoolState {
    uint8   poolType;             // pool type, can be ERC20 or ERC721
    uint256 t0Debt;               // [WAD] t0 debt in pool
    uint256 t0DebtInAuction;      // [WAD] t0 debt in auction within pool
    uint256 debt;                 // [WAD] total debt in pool, accrued in current block
    uint256 collateral;           // [WAD] total collateral pledged in pool
    uint256 inflator;             // [WAD] current pool inflator
    bool    isNewInterestAccrued; // true if new interest already accrued in current block
    uint256 rate;                 // [WAD] pool's current interest rate
    uint256 quoteTokenScale;      // [WAD] quote token scale of the pool. Same as quote token dust.
}

/*********************/
/*** Buckets State ***/
/*********************/

/// @dev Struct holding lender state.
struct Lender {
    uint256 lps;         // [WAD] Lender LP accumulator
    uint256 depositTime; // timestamp of last deposit
}

/// @dev Struct holding bucket state.
struct Bucket {
    uint256 lps;                        // [WAD] Bucket LP accumulator
    uint256 collateral;                 // [WAD] Available collateral tokens deposited in the bucket
    uint256 bankruptcyTime;             // Timestamp when bucket become insolvent, 0 if healthy
    mapping(address => Lender) lenders; // lender address to Lender struct mapping
}

/**********************/
/*** Deposits State ***/
/**********************/

/// @dev Struct holding deposits (Fenwick) values and scaling.
struct DepositsState {
    uint256[8193] values;  // Array of values in the FenwickTree.
    uint256[8193] scaling; // Array of values which scale (multiply) the FenwickTree accross indexes.
}

/*******************/
/*** Loans State ***/
/*******************/

/// @dev Struct holding loans state.
struct LoansState {
    Loan[] loans;
    mapping (address => uint)     indices;   // borrower address => loan index mapping
    mapping (address => Borrower) borrowers; // borrower address => Borrower struct mapping
}

/// @dev Struct holding loan state.
struct Loan {
    address borrower;       // borrower address
    uint96  thresholdPrice; // [WAD] Loan's threshold price.
}

/// @dev Struct holding borrower state.
struct Borrower {
    uint256 t0Debt;     // [WAD] Borrower debt time-adjusted as if it was incurred upon first loan of pool.
    uint256 collateral; // [WAD] Collateral deposited by borrower.
    uint256 t0Np;       // [WAD] Neutral Price time-adjusted as if it was incurred upon first loan of pool.
}

/**********************/
/*** Auctions State ***/
/**********************/

/// @dev Struct holding pool auctions state.
struct AuctionsState {
    uint96  noOfAuctions;                         // total number of auctions in pool
    address head;                                 // first address in auction queue
    address tail;                                 // last address in auction queue
    uint256 totalBondEscrowed;                    // [WAD] total amount of quote token posted as auction kick bonds
    mapping(address => Liquidation) liquidations; // mapping of borrower address and auction details
    mapping(address => Kicker)      kickers;      // mapping of kicker address and kicker balances
}

/// @dev Struct holding liquidation state.
struct Liquidation {
    address kicker;       // address that initiated liquidation
    uint96  bondFactor;   // [WAD] bond factor used to start liquidation
    uint96  kickTime;     // timestamp when liquidation was started
    address prev;         // previous liquidated borrower in auctions queue
    uint96  kickMomp;     // [WAD] Momp when liquidation was started
    address next;         // next liquidated borrower in auctions queue
    uint160 bondSize;     // [WAD] liquidation bond size
    uint96  neutralPrice; // [WAD] Neutral Price when liquidation was started
    bool    alreadyTaken; // true if take has been called on auction
}

/// @dev Struct holding kicker state.
struct Kicker {
    uint256 claimable; // [WAD] kicker's claimable balance
    uint256 locked;    // [WAD] kicker's balance of tokens locked in auction bonds
}

/******************************/
/*** Reserve Auctions State ***/
/******************************/

/// @dev Struct holding reserve auction state.
struct ReserveAuctionState {
    uint256 kicked;                            // Time a Claimable Reserve Auction was last kicked.
    uint256 unclaimed;                         // [WAD] Amount of claimable reserves which has not been taken in the Claimable Reserve Auction.
    uint256 latestBurnEventEpoch;              // Latest burn event epoch.
    uint256 totalAjnaBurned;                   // [WAD] Total ajna burned in the pool.
    uint256 totalInterestEarned;               // [WAD] Total interest earned by all lenders in the pool.
    mapping (uint256 => BurnEvent) burnEvents; // Mapping burnEventEpoch => BurnEvent.
}

/// @dev Struct holding burn event state.
struct BurnEvent {
    uint256 timestamp;     // time at which the burn event occured
    uint256 totalInterest; // [WAD] current pool interest accumulator `PoolCommons.accrueInterest().newInterest`
    uint256 totalBurned;   // [WAD] burn amount accumulator
}
// File: contracts/ajna/interfaces/pool/commons/IPoolTakerActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Pool Taker Actions
 */
interface IPoolTakerActions {

    /**
     *  @notice Called by actors to use quote token to arb higher-priced deposit off the book.
     *  @param  borrowerAddress_  Address of the borower take is being called upon.
     *  @param  depositTake_      If `true` then the take will happen at an auction price equal with bucket price. Auction price is used otherwise.
     *  @param  index_            Index of a bucket, likely the `HPB`, in which collateral will be deposited.
     */
    function bucketTake(
        address borrowerAddress_,
        bool    depositTake_,
        uint256 index_
    ) external;

    /**
     *  @notice Called by actors to purchase collateral from the auction in exchange for quote token.
     *  @param  borrowerAddress_  Address of the borower take is being called upon.
     *  @param  maxAmount_        Max amount of collateral that will be taken from the auction (`WAD` precision for `ERC20` pools, max number of `NFT`s for `ERC721` pools).
     *  @param  callee_           Identifies where collateral should be sent and where quote token should be obtained.
     *  @param  data_             If provided, take will assume the callee implements `IERC*Taker`.  Take will send collateral to 
     *                            callee before passing this data to `IERC*Taker.atomicSwapCallback`.  If not provided, 
     *                            the callback function will not be invoked.
     */
    function take(
        address        borrowerAddress_,
        uint256        maxAmount_,
        address        callee_,
        bytes calldata data_
    ) external;

    /***********************/
    /*** Reserve Auction ***/
    /***********************/

    /**
     *  @notice Purchases claimable reserves during a `CRA` using `Ajna` token.
     *  @param  maxAmount_ Maximum amount of quote token to purchase at the current auction price (`WAD` precision).
     *  @return amount_    Actual amount of reserves taken (`WAD` precision).
     */
    function takeReserves(
        uint256 maxAmount_
    ) external returns (uint256 amount_);

}
// File: contracts/ajna/interfaces/pool/erc20/IERC20Pool.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import { IPool }                     from '../IPool.sol';
import { IERC20PoolBorrowerActions } from './IERC20PoolBorrowerActions.sol';
import { IERC20PoolLenderActions }   from './IERC20PoolLenderActions.sol';
import { IERC20PoolImmutables }      from './IERC20PoolImmutables.sol';
import { IERC20PoolEvents }          from './IERC20PoolEvents.sol';

/**
 * @title ERC20 Pool
 */
interface IERC20Pool is
    IPool,
    IERC20PoolLenderActions,
    IERC20PoolBorrowerActions,
    IERC20PoolImmutables,
    IERC20PoolEvents
{

    /**
     *  @notice Initializes a new pool, setting initial state variables.
     *  @param  rate_ Initial interest rate of the pool (min accepted value 1%, max accepted value 10%).
     */
    function initialize(uint256 rate_) external;

    /**
     *  @notice Returns the minimum amount of collateral an actor may have in a bucket.
     *  @param  bucketIndex_ The bucket index for which the dust limit is desired, or `0` for pledged collateral.
     *  @return The dust limit for `bucketIndex_`.
     */
    function bucketCollateralDust(
        uint256 bucketIndex_
    ) external pure returns (uint256);

}

// File: contracts/ajna/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title ERC20 Pool Borrower Actions
 */
interface IERC20PoolBorrowerActions {

    /**
     *  @notice Called by borrowers to add collateral to the pool and/or borrow quote from the pool.
     *  @dev    Can be called by borrowers with either `0` `amountToBorrow_` or `0` `collateralToPledge_`, if borrower only wants to take a single action. 
     *  @param  borrowerAddress_    The borrower to whom collateral was pledged, and/or debt was drawn for.
     *  @param  amountToBorrow_     The amount of quote tokens to borrow (`WAD` precision).
     *  @param  limitIndex_         Lower bound of `LUP` change (if any) that the borrower will tolerate from a creating or modifying position.
     *  @param  collateralToPledge_ The amount of collateral to be added to the pool (`WAD` precision).
     */
    function drawDebt(
        address borrowerAddress_,
        uint256 amountToBorrow_,
        uint256 limitIndex_,
        uint256 collateralToPledge_
    ) external;

    /**
     *  @notice Called by borrowers to repay borrowed quote to the pool, and/or pull collateral form the pool.
     *  @dev    Can be called by borrowers with either `0` `maxQuoteTokenAmountToRepay_` or `0` `collateralAmountToPull_`, if borrower only wants to take a single action. 
     *  @param  borrowerAddress_            The borrower whose loan is being interacted with.
     *  @param  maxQuoteTokenAmountToRepay_ The max amount of quote tokens to repay (`WAD` precision).
     *  @param  collateralAmountToPull_     The max amount of collateral to be puled from the pool (`WAD` precision).
     *  @param  recipient_                  The address to receive amount of pulled collateral.
     *  @param  limitIndex_                 Ensures `LUP` has not moved far from state when borrower pulls collateral.
     */
    function repayDebt(
        address borrowerAddress_,
        uint256 maxQuoteTokenAmountToRepay_,
        uint256 collateralAmountToPull_,
        address recipient_,
        uint256 limitIndex_
    ) external;
}

// File: contracts/ajna/interfaces/pool/erc20/IERC20PoolEvents.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title ERC20 Pool Events
 */
interface IERC20PoolEvents {

    /**
     *  @notice Emitted when actor adds claimable collateral to a bucket.
     *  @param  actor     Recipient that added collateral.
     *  @param  index     Index at which collateral were added.
     *  @param  amount    Amount of collateral added to the pool (`WAD` precision).
     *  @param  lpAwarded Amount of `LP` awarded for the deposit (`WAD` precision).
     */
    event AddCollateral(
        address indexed actor,
        uint256 indexed index,
        uint256 amount,
        uint256 lpAwarded
    );

    /**
     *  @notice Emitted when borrower draws debt from the pool, or adds collateral to the pool.
     *  @param  borrower          The borrower to whom collateral was pledged, and/or debt was drawn for.
     *  @param  amountBorrowed    Amount of quote tokens borrowed from the pool (`WAD` precision).
     *  @param  collateralPledged Amount of collateral locked in the pool (`WAD` precision).
     *  @param  lup               `LUP` after borrow.
     */
    event DrawDebt(
        address indexed borrower,
        uint256 amountBorrowed,
        uint256 collateralPledged,
        uint256 lup
    );
}

// File: contracts/ajna/interfaces/pool/erc20/IERC20PoolImmutables.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title ERC20 Pool Immutables
 */
interface IERC20PoolImmutables {

    /**
     *  @notice Returns the `collateralScale` immutable.
     *  @return The precision of the collateral `ERC20` token based on decimals.
     */
    function collateralScale() external view returns (uint256);

}
// File: contracts/ajna/interfaces/pool/erc20/IERC20PoolLenderActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title ERC20 Pool Lender Actions
 */
interface IERC20PoolLenderActions {

    /**
     *  @notice Deposit claimable collateral into a specified bucket.
     *  @param  amountToAdd_ Amount of collateral to deposit (`WAD` precision).
     *  @param  index_       The bucket index to which collateral will be deposited.
     *  @param  expiry_      Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price.
     *  @return bucketLP_    The amount of `LP` awarded for the added collateral (`WAD` precision).
     */
    function addCollateral(
        uint256 amountToAdd_,
        uint256 index_,
        uint256 expiry_
    ) external returns (uint256 bucketLP_);
}
// File: contracts/ajna/interfaces/pool/IERC3156FlashBorrower.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

interface IERC3156FlashBorrower {

    /**
     * @dev    Receive a flash loan.
     * @param  initiator The initiator of the loan.
     * @param  token     The loan currency.
     * @param  amount    The amount of tokens lent (token precision).
     * @param  fee       The additional amount of tokens to repay.
     * @param  data      Arbitrary data structure, intended to contain user-defined parameters.
     * @return The `keccak256` hash of `ERC3156FlashBorrower.onFlashLoan`
     */
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes   calldata data
    ) external returns (bytes32);
}
// File: contracts/ajna/interfaces/pool/IERC3156FlashLender.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;
import { IERC3156FlashBorrower } from "./IERC3156FlashBorrower.sol";


interface IERC3156FlashLender {

    /**
     * @dev    The amount of currency available to be lent.
     * @param  token_ The loan currency.
     * @return The amount of `token` that can be borrowed (token precision).
     */
    function maxFlashLoan(
        address token_
    ) external view returns (uint256);

    /**
     * @dev    The fee to be charged for a given loan.
     * @param  token_    The loan currency.
     * @param  amount_   The amount of tokens lent (token precision).
     * @return The amount of `token` to be charged for the loan (token precision), on top of the returned principal .
     */
    function flashFee(
        address token_,
        uint256 amount_
    ) external view returns (uint256);

    /**
     * @dev    Initiate a flash loan.
     * @param  receiver_ The receiver of the tokens in the loan, and the receiver of the callback.
     * @param  token_    The loan currency.
     * @param  amount_   The amount of tokens lent (token precision).
     * @param  data_     Arbitrary data structure, intended to contain user-defined parameters.
     * @return `True` when successful flashloan, `false` otherwise.
     */
    function flashLoan(
        IERC3156FlashBorrower receiver_,
        address token_,
        uint256 amount_,
        bytes   calldata data_
    ) external returns (bool);
}
// File: contracts/ajna/interfaces/pool/IPool.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import { IPoolBorrowerActions } from './commons/IPoolBorrowerActions.sol';
import { IPoolLPActions }       from './commons/IPoolLPActions.sol';
import { IPoolLenderActions }   from './commons/IPoolLenderActions.sol';
import { IPoolKickerActions }   from './commons/IPoolKickerActions.sol';
import { IPoolTakerActions }    from './commons/IPoolTakerActions.sol';
import { IPoolSettlerActions }  from './commons/IPoolSettlerActions.sol';

import { IPoolImmutables }      from './commons/IPoolImmutables.sol';
import { IPoolState }           from './commons/IPoolState.sol';
import { IPoolDerivedState }    from './commons/IPoolDerivedState.sol';
import { IPoolEvents }          from './commons/IPoolEvents.sol';
import { IPoolErrors }          from './commons/IPoolErrors.sol';
import { IERC3156FlashLender }  from './IERC3156FlashLender.sol';

/**
 * @title Base Pool Interface
 */
interface IPool is
    IPoolBorrowerActions,
    IPoolLPActions,
    IPoolLenderActions,
    IPoolKickerActions,
    IPoolTakerActions,
    IPoolSettlerActions,
    IPoolImmutables,
    IPoolState,
    IPoolDerivedState,
    IPoolEvents,
    IPoolErrors,
    IERC3156FlashLender
{

}

/// @dev Pool type enum - `ERC20` and `ERC721`
enum PoolType { ERC20, ERC721 }

/// @dev `ERC20` token interface.
interface IERC20Token {
    function balanceOf(address account) external view returns (uint256);
    function burn(uint256 amount) external;
    function decimals() external view returns (uint8);
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

/// @dev `ERC721` token interface.
interface IERC721Token {
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
}

// File: contracts/ajna/interfaces/position/IPositionManager.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import { IPositionManagerOwnerActions } from './IPositionManagerOwnerActions.sol';
import { IPositionManagerState }        from './IPositionManagerState.sol';
import { IPositionManagerDerivedState } from './IPositionManagerDerivedState.sol';
import { IPositionManagerErrors }       from './IPositionManagerErrors.sol';
import { IPositionManagerEvents }       from './IPositionManagerEvents.sol';

/**
 *  @title Position Manager Interface
 */
interface IPositionManager is
    IPositionManagerOwnerActions,
    IPositionManagerState,
    IPositionManagerDerivedState,
    IPositionManagerErrors,
    IPositionManagerEvents
{

}

// File: contracts/ajna/interfaces/position/IPositionManagerDerivedState.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Positions Manager Derived State
 */
interface IPositionManagerDerivedState {

    /**
     *  @notice Returns the `LP` accrued to a given `tokenId`, bucket pairing.
     *  @dev    Nested mappings aren't returned normally as part of the default getter for a mapping.
     *  @param  tokenId_ Unique `ID` of token.
     *  @param  index_   Index of bucket to check `LP` balance of.
     *  @return lp_      Balance of `LP` in the bucket for this position.
    */
    function getLP(
        uint256 tokenId_,
        uint256 index_
    ) external view returns (uint256 lp_);

    /**
     *  @notice Returns an array of bucket indexes in which an `NFT` has liquidity.
     *  @dev    Potentially includes buckets that have been bankrupted.
     *  @param  tokenId_  Unique `ID` of token.
     *  @return Array of bucket indexes.
    */
    function getPositionIndexes(
        uint256 tokenId_
    ) external view returns (uint256[] memory);

    /**
     *  @notice Returns an array of bucket indexes in which an `NFT` has liquidity, with bankrupt buckets removed.
     *  @param  tokenId_ Unique `ID` of token.
     *  @return Array of bucket indexes filtered for active liquidity.
    */
    function getPositionIndexesFiltered(
        uint256 tokenId_
    ) external view returns (uint256[] memory);

    /**
     *  @notice Returns information about a given `NFT`.
     *  @param  tokenId_ Unique `ID` of token.
     *  @param  index_   Bucket index to check for position information.
     *  @return `LP` in bucket.
     *  @return Position's deposit time.
    */
    function getPositionInfo(
        uint256 tokenId_,
        uint256 index_
    ) external view returns (uint256, uint256);

    /**
     *  @notice Returns the pool address associated with a positions `NFT`.
     *  @param  tokenId_ The token id of the positions `NFT`.
     *  @return Pool address associated with the `NFT`.
     */
    function poolKey(
        uint256 tokenId_
    ) external view returns (address);

    /**
     *  @notice Checks if a given `pool_` address is an Ajna pool.
     *  @param  pool_       Address of the `Ajna` pool.
     *  @param  subsetHash_ Factory's subset hash pool.
     *  @return isAjnaPool_ `True` if the address to check is an Ajna pool.
    */
    function isAjnaPool(
        address pool_,
        bytes32 subsetHash_
    ) external view returns (bool isAjnaPool_);

    /**
     *  @notice Checks if a given `tokenId` has a given position bucket.
     *  @param  tokenId_           Unique `ID` of token.
     *  @param  index_             Index of bucket to check if in position buckets.
     *  @return bucketInPosition_  `True` if tokenId has the position bucket.
    */
    function isIndexInPosition(
        uint256 tokenId_,
        uint256 index_
    ) external view returns (bool bucketInPosition_);

    /**
     *  @notice Checks if a tokenId has a position in a bucket that was bankrupted.
     *  @param  tokenId_           Unique ID of token.
     *  @param  index_             Index of bucket to check for bankruptcy.
     *  @return isBankrupt_        True if the position in the bucket was bankrupted.
    */
    function isPositionBucketBankrupt(
        uint256 tokenId_,
        uint256 index_
    ) external view returns (bool isBankrupt_);
}

// File: contracts/ajna/interfaces/position/IPositionManagerErrors.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Positions Manager Errors
 */
interface IPositionManagerErrors {

    /**
     * @notice User attempting to memorialize position with an allowance set too low.
     */
    error AllowanceTooLow();

    /**
     * @notice User attempting to utilize `LP` from a bankrupt bucket.
     */
    error BucketBankrupt();

    /**
     *  @notice Can't deploy with `ERC20` or `ERC721` factory address `0x`.
     */
    error DeployWithZeroAddress();

    /**
     * @notice User attempting to burn a `LP` `NFT` before removing liquidity.
     */
    error LiquidityNotRemoved();

    /**
     * @notice User not authorized to interact with the specified `NFT`.
     */
    error NoAuth();

    /**
     * @notice `NFT` with provided ID does not exist.
     */
    error NoToken();

    /**
     * @notice User attempted to mint an `NFT` pointing to a pool that wasn't deployed by an `Ajna` factory.
     */
    error NotAjnaPool();

    /**
     * @notice User failed to remove position from their `NFT`.
     */
    error RemovePositionFailed();

    /**
     * @notice User attempting to interact with a pool that doesn't match the pool associated with the `tokenId`.
     */
    error WrongPool();
}
// File: contracts/ajna/interfaces/position/IPositionManagerEvents.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Positions Manager Events
 */
interface IPositionManagerEvents {

    /**
     *  @notice Emitted when an existing `NFT` was burned.
     *  @param  lender  Lender address.
     *  @param  tokenId The token id of the `NFT` that was burned.
     */
    event Burn(
        address indexed lender,
        uint256 indexed tokenId
    );

    /**
     *  @notice Emitted when existing positions were memorialized for a given `NFT`.
     *  @param  tokenId The `tokenId` of the `NFT`.
     *  @param  indexes Bucket indexes of memorialized positions.
     */
    event MemorializePosition(
        address indexed lender,
        uint256 tokenId,
        uint256[] indexes
    );

    /**
     *  @notice Emitted when representative `NFT` minted.
     *  @param  lender  Lender address.
     *  @param  pool    Pool address.
     *  @param  tokenId The `tokenId` of the newly minted `NFT`.
     */
    event Mint(
        address indexed lender,
        address indexed pool,
        uint256 tokenId
    );

    /**
     *  @notice Emitted when a position's liquidity is moved between buckets.
     *  @param  lender         Lender address.
     *  @param  tokenId        The `tokenId` of the newly minted `NFT`.
     *  @param  fromIndex      Index of bucket from where liquidity is moved.
     *  @param  toIndex        Index of bucket where liquidity is moved.
     *  @param  lpRedeemedFrom Amount of `LP` removed from the `from` bucket.
     *  @param  lpAwardedTo    Amount of `LP` credited to the `to` bucket.
     */
    event MoveLiquidity(
        address indexed lender,
        uint256 tokenId,
        uint256 fromIndex,
        uint256 toIndex,
        uint256 lpRedeemedFrom,
        uint256 lpAwardedTo
    );

    /**
     *  @notice Emitted when existing positions were redeemed for a given `NFT`.
     *  @param  tokenId The `tokenId` of the `NFT`.
     *  @param  indexes Bucket indexes of redeemed positions.
     */
    event RedeemPosition(
        address indexed lender,
        uint256 tokenId,
        uint256[] indexes
    );
}
// File: contracts/ajna/interfaces/position/IPositionManagerOwnerActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Positions Manager Owner Actions
 */
interface IPositionManagerOwnerActions {

    /**
     *  @notice Called by owners to burn an existing `NFT`.
     *  @dev    Requires that all `LP` have been removed from the `NFT `prior to calling.
     *  @param  pool_    The pool address associated with burned positions NFT
     *  @param  tokenId_ The tokenId of the positions NFT to burn.
     */
    function burn(
        address pool_,
        uint256 tokenId_
    ) external;

    /**
     *  @notice Called to memorialize existing positions with a given NFT.
     *  @dev    The array of buckets is expected to be constructed off chain by scanning events for that lender.
     *  @dev    The NFT must have already been created, and the number of buckets to be memorialized at a time determined by function caller.
     *  @dev    An additional call is made to the pool to transfer the LP from their previous owner, to the Position Manager.
     *  @dev    `Pool.increaseLPAllowance` must be called prior to calling this method in order to allow Position manager contract to transfer LP to be memorialized.
     *  @param  pool_    The pool address associated with positions NFT.
     *  @param  tokenId_ The tokenId of the positions NFT.
     *  @param  indexes_ The array of bucket indexes to memorialize positions.
     */
    function memorializePositions(
        address pool_,
        uint256 tokenId_,
        uint256[] calldata indexes_
    ) external;

    /**
     *  @notice Called by owners to mint and receive an `Ajna` Position `NFT`.
     *  @dev    Position `NFT`s can only be minited with an association to pools that have been deployed by the `Ajna` `ERC20PoolFactory` or `ERC721PoolFactory`.
     *  @param  pool_           The pool address associated with minted positions NFT.
     *  @param  recipient_      Lender address.
     *  @param  poolSubsetHash_ Hash of pool information used to track pool in the factory after deployment.
     *  @return tokenId_ The `tokenId` of the newly minted `NFT`.
     */
    function mint(
        address pool_,
        address recipient_,
        bytes32 poolSubsetHash_
    ) external returns (uint256 tokenId_);

    /**
     *  @notice Called by owners to move liquidity between two buckets.
     *  @param  pool_             The pool address associated with positions NFT.
     *  @param  tokenId_          The tokenId of the positions NFT.
     *  @param  fromIndex_        The bucket index from which liquidity should be moved.
     *  @param  toIndex_          The bucket index to which liquidity should be moved.
     *  @param  expiry_           Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price.
     *  @param  revertIfBelowLup_ The tx will revert if quote token is moved from above the `LUP` to below the `LUP` (and avoid paying fee for move below `LUP`).
     */
    function moveLiquidity(
        address pool_,
        uint256 tokenId_,
        uint256 fromIndex_,
        uint256 toIndex_,
        uint256 expiry_,
        bool    revertIfBelowLup_
    ) external;

    /**
     *  @notice Called to redeem existing positions with a given `NFT`.
     *  @dev    The array of buckets is expected to be constructed off chain by scanning events for that lender.
     *  @dev    The `NFT` must have already been created, and the number of buckets to be memorialized at a time determined by function caller.
     *  @dev    An additional call is made to the pool to transfer the `LP` Position Manager to owner.
     *  @dev    `Pool.approveLPTransferors` must be called prior to calling this method in order to allow `Position manager` contract to transfer redeemed `LP`.
     *  @param  pool_    The pool address associated with positions NFT.
     *  @param  tokenId_ The tokenId of the positions NFT.
     *  @param  indexes_ The array of bucket indexes to memorialize positions.
     */
    function redeemPositions(
        address pool_,
        uint256 tokenId_,
        uint256[] calldata indexes_
    ) external;

}

// File: contracts/ajna/interfaces/position/IPositionManagerState.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import { EnumerableSet } from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';

/**
 * @title Positions Manager State
 */
interface IPositionManagerState {

    /**
    * @dev Struct holding Position `LP` state.
    * @param lps         [WAD] position LP.
    * @param depositTime Deposit time for position
    */
    struct Position {
        uint256 lps;
        uint256 depositTime;
    }

    /**
    * @dev Struct tracking a position token info.
    * @param pool            The pool address associated with the position.
    * @param positionIndexes Mapping tracking indexes to which a position is associated.
    * @param positions       Mapping tracking a positions state in a bucket index.
    */
    struct TokenInfo {
        address pool;
        EnumerableSet.UintSet positionIndexes;
        mapping(uint256 index => Position) positions;
    }

}

// File: contracts/ajna/interfaces/rewards/IRewardsManager.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import { IRewardsManagerOwnerActions } from './IRewardsManagerOwnerActions.sol';
import { IRewardsManagerState }        from './IRewardsManagerState.sol';
import { IRewardsManagerDerivedState } from './IRewardsManagerDerivedState.sol';
import { IRewardsManagerEvents }       from './IRewardsManagerEvents.sol';
import { IRewardsManagerErrors }       from './IRewardsManagerErrors.sol';

interface IRewardsManager is
    IRewardsManagerOwnerActions,
    IRewardsManagerState,
    IRewardsManagerDerivedState,
    IRewardsManagerErrors,
    IRewardsManagerEvents
{

}

// File: contracts/ajna/interfaces/rewards/IRewardsManagerDerivedState.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Rewards Manager Derived State
 */
interface IRewardsManagerDerivedState {

    /**
     *  @notice Calculate the amount of rewards that have been accumulated by a staked `NFT`.
     *  @param  tokenId_      `ID` of the staked `LP` `NFT`.
     *  @param  epochToClaim_ The end burn epoch to calculate rewards for (rewards calculation starts from the last claimed epoch).
     *  @return The amount of rewards earned by the staked `NFT`.
     */
    function calculateRewards(
        uint256 tokenId_,
        uint256 epochToClaim_
    ) external view returns (uint256);

}

// File: contracts/ajna/interfaces/rewards/IRewardsManagerErrors.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Rewards Manager Errors
 */
interface IRewardsManagerErrors {
    /**
     *  @notice User attempted to claim rewards multiple times.
     */
    error AlreadyClaimed();

    /**
     *  @notice User attempted to claim rewards for an epoch that is not yet available.
     */
    error EpochNotAvailable();

    /**
     *  @notice Insufficient Token Balance in contract to transfer rewards
     */
    error InsufficientLiquidity();

    /**
     *  @notice User provided move index params that didn't match in size.
     */
    error MoveStakedLiquidityInvalid();

    /**
     * @notice User attempted to update exchange rates for a pool that wasn't deployed by an `Ajna` factory.
     */
    error NotAjnaPool();

    /**
     *  @notice User attempted to interact with an `NFT` they aren't the owner of.
     */
    error NotOwnerOfDeposit();

    /**
     *  @notice Can't deploy with `Ajna` token or position manager address `0x`.
     */
    error DeployWithZeroAddress();
}
// File: contracts/ajna/interfaces/rewards/IRewardsManagerEvents.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Rewards Manager Events
 */
interface IRewardsManagerEvents {

     /**
     *  @notice Emitted when lender claims rewards that have accrued to their staked `NFT`.
     *  @param  owner         Owner of the staked `NFT`.
     *  @param  ajnaPool      Address of the `Ajna` pool the `NFT` corresponds to.
     *  @param  tokenId       `ID` of the staked `NFT`.
     *  @param  epochsClaimed Array of burn epochs claimed.
     *  @param  amount        The amount of `Ajna` tokens claimed by the staker.
     */
    event ClaimRewards(
        address indexed owner,
        address indexed ajnaPool,
        uint256 indexed tokenId,
        uint256[] epochsClaimed,
        uint256 amount
    );

     /**
     *  @notice Emitted when moves liquidity in a staked `NFT` between buckets.
     *  @param  tokenId     `ID` of the staked `NFT`.
     *  @param  fromIndexes Array of indexes from which liquidity was moved.
     *  @param  toIndexes   Array of indexes to which liquidity was moved.
     */
    event MoveStakedLiquidity(
        uint256 tokenId,
        uint256[] fromIndexes,
        uint256[] toIndexes
    );

    /**
     *  @notice Emitted when lender stakes their `LP` `NFT` in the rewards contract.
     *  @param  owner    Owner of the staked `NFT`.
     *  @param  ajnaPool Address of the `Ajna` pool the `NFT` corresponds to.
     *  @param  tokenId  `ID` of the staked `NFT`.
     */
    event Stake(
        address indexed owner,
        address indexed ajnaPool,
        uint256 indexed tokenId
    );

    /**
     *  @notice Emitted when someone records the latest exchange rate for a bucket in a pool, and claims the associated reward.
     *  @param  caller          Address of the recorder. The address which will receive an update reward, if applicable.
     *  @param  ajnaPool        Address of the `Ajna` pool whose exchange rates are being updated.
     *  @param  indexesUpdated  Array of bucket indexes whose exchange rates are being updated.
     *  @param  rewardsClaimed  Amount of `Ajna` tokens claimed by the recorder as a reward for updating each bucket index.
     */
    event UpdateExchangeRates(
        address indexed caller,
        address indexed ajnaPool,
        uint256[] indexesUpdated,
        uint256 rewardsClaimed
    );

    /**
     *  @notice Emitted when lender withdraws their `LP` `NFT` from the rewards contract.
     *  @param  owner    Owner of the staked `NFT`.
     *  @param  ajnaPool Address of the `Ajna` pool the `NFT` corresponds to.
     *  @param  tokenId  `ID` of the staked `NFT`.
     */
    event Unstake(
        address indexed owner,
        address indexed ajnaPool,
        uint256 indexed tokenId
    );
}
// File: contracts/ajna/interfaces/rewards/IRewardsManagerOwnerActions.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Rewards Manager Owner Actions
 */
interface IRewardsManagerOwnerActions {

    /**
     *  @notice Claim `Ajna` token rewards that have accrued to a staked `LP` `NFT`.
     *  @dev    Updates exchange rates for each bucket the `NFT` is associated with.
     *  @dev    Reverts with `InsufficientLiquidity` if calculated rewards or contract balance is below specified min amount to receive limit.
     *  @param  tokenId_      `ID` of the staked `LP` `NFT`.
     *  @param  epochToClaim_ The burn epoch to claim rewards for.
     *  @param  minAmount_    Minimum amount to be received by rewards claimer.
     */
    function claimRewards(
        uint256 tokenId_,
        uint256 epochToClaim_,
        uint256 minAmount_
    ) external;

    /**
     *  @notice Stake a `LP` `NFT` into the rewards contract.
     *  @dev    Updates exchange rates for each bucket the `NFT` is associated with.
     *  @param  tokenId_ `ID` of the `LP` `NFT` to stake in the `Rewards contract.
     */
    function stake(
        uint256 tokenId_
    ) external;

    /**
     *  @notice Withdraw a staked `LP` `NFT` from the rewards contract.
     *  @notice If rewards are available, claim all available rewards before withdrawal.
     *  @param  tokenId_ `ID` of the staked `LP` `NFT`.
     */
    function unstake(
        uint256 tokenId_
    ) external;

    /**
     *  @notice Withdraw a staked `LP` `NFT` from the rewards contract without claiming any rewards before withdrawal.
     *  @param  tokenId_ `ID` of the staked `LP` `NFT`.
     */
    function emergencyUnstake(
        uint256 tokenId_
    ) external;

    /**
     *  @notice Update the exchange rate of a list of buckets.
     *  @dev    Caller can claim `5%` of the rewards that have accumulated to each bucket since the last burn event, if it hasn't already been updated.
     *  @param  pool_       Address of the pool whose exchange rates are being updated.
     *  @param  subsetHash_ Factory's subset hash pool that dpeloyed the Ajna pool. Used to validate that the `pool_` address is a legit Ajna pool.
     *  @param  indexes_    List of bucket indexes to be updated.
     *  @return Returns reward amount for updating bucket exchange rates.
     */
    function updateBucketExchangeRatesAndClaim(
        address pool_,
        bytes32 subsetHash_,
        uint256[] calldata indexes_
    ) external returns (uint256);

}
// File: contracts/ajna/interfaces/rewards/IRewardsManagerState.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title Rewards Manager State
 */
interface IRewardsManagerState {

    /**
     *  @notice Track whether a bucket's exchange rate has been updated in a given burn event epoch.
     *  @param  pool_        The pool to check the update status of.
     *  @param  bucketIndex_ The bucket index to check the update status of.
     *  @param  epoch_   The burn epoch to check the bucket index in.
     *  @return `True` if the buckets exchange rate was updated in the given epoch, else false.
     */
    function isBucketUpdated(
        address pool_,
        uint256 bucketIndex_,
        uint256 epoch_
    ) external view returns (bool);

    /**
     *  @notice Track whether a depositor has claimed rewards for a given burn event epoch.
     *  @param  tokenId_ ID of the staked `LP` `NFT`.
     *  @param  epoch_   The burn epoch to track if rewards were claimed.
     *  @return `True` if rewards were claimed for the given epoch, else false.
     */
    function isEpochClaimed(
        uint256 tokenId_,
        uint256 epoch_
    ) external view returns (bool);

    /**
     *  @notice Track the total amount of rewards that have been claimed for a given epoch.
     *  @param  epoch_   The burn epoch to track if rewards were claimed.
     *  @return The amount of rewards claimed in given epoch.
     */
    function rewardsClaimed(
        uint256 epoch_
    ) external view returns (uint256);

    /**
     *  @notice Track the total amount of rewards that have been claimed for a given burn event's bucket updates.
     *  @param  epoch_   The burn epoch to track if rewards were claimed.
     *  @return The amount of update rewards claimed in given epoch.
     */
    function updateRewardsClaimed(
        uint256 epoch_
    ) external view returns (uint256);

    /**
     *  @notice Retrieve information about a given stake.
     *  @param  tokenId_          `ID` of the `NFT` staked in the rewards contract to retrieve information about.
     *  @return owner_            The owner of a given `NFT` stake.
     *  @return pool_             The `Pool` the `NFT` represents positions in.
     *  @return lastClaimedEpoch_ The last burn epoch in which the owner of the `NFT` claimed rewards.
     */
    function getStakeInfo(
        uint256 tokenId_
    ) external view returns (address owner_, address pool_, uint256 lastClaimedEpoch_);

    /**
     *  @notice Retrieve information about recorded `LP` and rate values for a given bucket and a given stake, at stake time.
     *  @param  tokenId_  `ID` of the `NFT` staked in the rewards contract to retrieve information about.
     *  @param  bucketId_ `ID` of the bucket to retrieve recorded information at stake time.
     *  @return `LP` amount (in `WAD`) the `NFT` owner is entitled in current bucket at the time of staking.
     *  @return Current bucket exchange rate (`WAD`) at the time of staking.
     */
    function getBucketStateStakeInfo(
        uint256 tokenId_,
        uint256 bucketId_
    ) external view returns (uint256, uint256);

}

/*********************/
/*** State Structs ***/
/*********************/

/// @dev Struct holding stake info state.
struct StakeInfo {
    address ajnaPool;                         // address of the Ajna pool the NFT corresponds to
    uint96  lastClaimedEpoch;                 // last epoch the stake claimed rewards
    address owner;                            // owner of the LP NFT
    uint96  stakingEpoch;                     // epoch at staking time
    mapping(uint256 => BucketState) snapshot; // the LP NFT's balances and exchange rates in each bucket at the time of staking
}

/// @dev Struct holding bucket state at stake time.
struct BucketState {
    uint256 lpsAtStakeTime;  // [WAD] LP amount the NFT owner is entitled in current bucket at the time of staking
    uint256 rateAtStakeTime; // [WAD] current bucket exchange rate at the time of staking
}

// File: contracts/interfaces/ajna/IAjnaPoolUtilsInfo.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;

interface IAjnaPoolUtilsInfo {
    function priceToIndex(uint256 price_) external pure returns (uint256);

    function borrowerInfo(
        address pool_,
        address borrower_
    ) external view returns (uint256 debt_, uint256 collateral_, uint256 index_);

    function poolPricesInfo(
        address ajnaPool_
    )
        external
        view
        returns (uint256 hpb_, uint256 hpbIndex_, uint256 htp_, uint256 htpIndex_, uint256 lup_, uint256 lupIndex_);

    function lpToQuoteTokens(
        address ajnaPool_,
        uint256 lp_,
        uint256 index_
    ) external view returns (uint256 quoteAmount_);
}

// File: contracts/interfaces/dpm/IAccountGuard.sol



// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.0;

interface IAccountGuard {
    function owners(address) external view returns (address);

    function owner() external view returns (address);

    function setWhitelist(address target, bool status) external;

    function canCall(
        address proxy,
        address operator
    ) external view returns (bool);

    function permit(address caller, address target, bool allowance) external;
    function isWhitelisted(address target) external view returns (bool);
    function isWhitelistedSend(address target) external view returns (bool);
}

// File: contracts/interfaces/tokens/IWETH.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;

interface IWETH {
    function deposit() external payable;
    function transfer(address to, uint value) external returns (bool);
    function withdraw(uint) external;
}