// File: contracts/governance/GovernanceControl.sol



// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.4;

import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../interfaces/IGovernance.sol";

/**
 * @title GovernanceControl contract.
 *
 * @dev Contract module which provides a basic access control mechanism, where
 * contract methods can be restricted to execution to only by governance executor
 */
abstract contract GovernanceControl is Initializable, ContextUpgradeable {
    /// Governance that controls inherited contract.
    IGovernance internal _governance;
    /// Governance executor.
    address private _executor;

    /**
     * @dev Throws if called by any address other than the governance executor.
     *
     * Requirements:
     * - caller must be governance executor.
     */
    modifier onlyGovernance() {
        require(_executor == _msgSender(), "GovernanceControl: only executor");
        _;
    }

    function setGovernance(address governance_) external virtual onlyGovernance {
        require(governance_ != address(0) && address(_governance) != governance_, "invalid address");
        _governance = IGovernance(governance_);
    }

    function setExecutor(address executor_) external virtual onlyGovernance {
        require(executor_ != address(0) && _executor != executor_, "invalid address");
        _executor = executor_;
    }

    function governance() external view virtual returns (address) {
        return address(_governance);
    }

    function executor() external view virtual returns (address) {
        return _executor;
    }

    function __GovernanceControl_init_unchained(address governance_, address executor_) internal initializer {
        require(governance_ != address(0) && executor_ != address(0), "invalid addresses");
        _governance = IGovernance(governance_);
        _executor = executor_;
    }

    function __GovernanceControl_init(address governance_, address executor_) internal initializer {
        __Context_init();
        __GovernanceControl_init_unchained(governance_, executor_);
    }
}

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



// SPDX-License-Identifier: MIT

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

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

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

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
    uint256[50] private __gap;
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

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

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        require(_initializing || !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }
}

// File: contracts/interfaces/IGovernance.sol



// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.4;

/**
 * @title Governance interface.
 * @author DeOrderBook
 * @custom:license
 *
 *                Copyright (c) 2023 DeOrderBook
 *
 *           Licensed under the Apache License, Version 2.0 (the "License");
 *           you may not use this file except in compliance with the License.
 *           You may obtain a copy of the License at
 *
 *               http://www.apache.org/licenses/LICENSE-2.0
 *
 *           Unless required by applicable law or agreed to in writing, software
 *           distributed under the License is distributed on an "AS IS" BASIS,
 *           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *           See the License for the specific language governing permissions and
 *           limitations under the License.
 *
 * @dev Interface for managing the governance process.
 */
interface IGovernance {
    /**
     * @notice Get the receipt for a given voter on a given proposal
     * @dev Returns the receipt for the specified voter on the specified proposal
     * @param proposalId The ID of the proposal to retrieve the receipt for
     * @param voter The address of the voter to retrieve the receipt for
     * @return The receipt as a tuple (votes, status)
     */
    function getReceipt(uint256 proposalId, address voter) external view returns (uint256, uint8);

    /**
     * @notice Check if a given proposal has been successful
     * @dev Returns a boolean indicating if the specified proposal has been successful
     * @param proposalId The ID of the proposal to check
     * @return A boolean indicating if the proposal was successful
     */
    function isProposalSuccessful(uint256 proposalId) external view returns (bool);

    /**
     * @notice Get the snapshot block number for a given proposal
     * @dev Returns the snapshot block number for the specified proposal
     * @param proposalId The ID of the proposal to retrieve the snapshot for
     * @return The snapshot block number for the proposal
     */
    function proposalSnapshot(uint256 proposalId) external view returns (uint256);

    /**
     * @notice Get the address of the governance executor
     * @dev Returns the address of the governance executor
     * @return The address of the governance executor
     */
    function executor() external view returns (address);
}

// File: contracts/governance/Treasury.sol



// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "./GovernanceControl.sol";

/**
 * @title Treasury contract.
 *
 * @dev Treasury contract allows to hold, receive and use ERC20 funds.
 */
contract Treasury is Initializable, GovernanceControl {
    using SafeERC20Upgradeable for IERC20Upgradeable;

    event Received(address from, address asset, uint256 amount);
    event Sent(address to, address asset, uint256 amount);
    event IncreasedAllowance(address spender, address asset, uint256 amount);
    event DecreasedAllowance(address spender, address asset, uint256 amount);

    /**
     * @dev Receive ETH fallback payable function.
     */
    receive() external payable virtual {}

    function initialize(address governance_, address executor_) external virtual initializer {
        __Treasury_init(governance_, executor_);
    }

    function increaseAllowance(
        address spender,
        address asset,
        uint256 amount
    ) external virtual onlyGovernance {
        IERC20Upgradeable(asset).safeIncreaseAllowance(spender, amount);
        emit IncreasedAllowance(spender, asset, amount);
    }

    function decreaseAllowance(
        address spender,
        address asset,
        uint256 amount
    ) external virtual onlyGovernance {
        IERC20Upgradeable(asset).safeDecreaseAllowance(spender, amount);
        emit DecreasedAllowance(spender, asset, amount);
    }

    function transfer(
        address to,
        address asset,
        uint256 amount
    ) external virtual onlyGovernance {
        if (asset == address(0)) {
            payable(to).call{value: amount};
        } else {
            IERC20Upgradeable(asset).safeTransfer(to, amount);
        }
        emit Sent(to, asset, amount);
    }

    function __Treasury_init(address governance_, address executor_) internal initializer {
        __GovernanceControl_init(governance_, executor_);
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @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 `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @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);
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../../../utils/AddressUpgradeable.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 SafeERC20Upgradeable {
    using AddressUpgradeable for address;

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

    function safeTransferFrom(
        IERC20Upgradeable 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(
        IERC20Upgradeable 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(
        IERC20Upgradeable 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(
        IERC20Upgradeable 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));
        }
    }

    /**
     * @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(IERC20Upgradeable 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-upgradeable/utils/AddressUpgradeable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 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 functionCall(target, data, "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");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(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) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason 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 {
            // 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

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20Upgradeable.sol";
import "./extensions/IERC20MetadataUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The default value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    function __ERC20_init(string memory name_, string memory symbol_) internal initializer {
        __Context_init_unchained();
        __ERC20_init_unchained(name_, symbol_);
    }

    function __ERC20_init_unchained(string memory name_, string memory symbol_) internal initializer {
        _name = name_;
        _symbol = symbol_;
    }

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

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overridden;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        unchecked {
            _approve(sender, _msgSender(), currentAllowance - amount);
        }

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(_msgSender(), spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `sender` to `recipient`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

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

        emit Transfer(sender, recipient, amount);

        _afterTokenTransfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

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

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

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

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

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

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

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
    uint256[45] private __gap;
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";

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

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

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

// File: @openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

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

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    function __Pausable_init() internal initializer {
        __Context_init_unchained();
        __Pausable_init_unchained();
    }

    function __Pausable_init_unchained() internal initializer {
        _paused = false;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        require(!paused(), "Pausable: paused");
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        require(paused(), "Pausable: not paused");
        _;
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
    uint256[49] private __gap;
}

// File: contracts/staking/DOBStakingPool.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.4;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "../interfaces/IStakingPoolRewarder.sol";
import "../interfaces/IDOBStakingPool.sol";
import "../interfaces/IOption.sol";
import "../interfaces/ITokenKeeper.sol";

/**
 * @title DOBStakingPool
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice The DOBStakingPool contract manages staking and reward distribution for $DOB tokens.
 * @dev It includes functionalities for staking $DOB, claiming rewards, withdrawing and updating parameters.
 */
contract DOBStakingPool is OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, IDOBStakingPool {
    using SafeMathUpgradeable for uint256;

    /**
     * @notice Address of the DOB token.
     * @dev This is the token that will be staked in the pool.
     */
    address public DOB;

    /**
     * @notice Address where the fees are collected.
     * @dev This is generally a multisig wallet or a treasury account.
     */
    address public feeCollector;

    /**
     * @notice Address where bullets are collected.
     * @dev Bullets are a secondary token used in the ecosystem.
     */
    address public bulletCollector;

    /**
     * @notice Address of the reward dispatcher.
     * @dev This address is responsible for calculating and distributing rewards.
     */
    address public rewardDispatcher;

    /**
     * @notice Address of the worker.
     * @dev This address performs certain actions that are restricted to workers.
     */
    address public worker;

    /**
     * @notice Address of the OptionFactory contract.
     * @dev This is the contract responsible for creating and managing options.
     */
    address public optionFactory;

    /**
     * @notice Address of the uHODL contract.
     * @dev This is one of the contracts that will receive rewards.
     */
    address public uHODL;

    /**
     * @notice Address of the bHODL contract.
     * @dev This is one of the contracts that will receive rewards.
     */
    address public bHODL;

    /**
     * @notice Array of all activated options.
     * @dev Each option includes an optionAddress, bullet, sniper and bulletBalance.
     */
    OptionData[] public activatedOptions;

    /**
     * @notice The bullet reward threshold.
     * @dev Users with a daily stake more than this threshold will receive bullet rewards.
     */
    uint256 public bulletRewardThreshold;

    /**
     * @notice The extension of lock days.
     * @dev This value affects the duration of staking.
     */
    uint256 public extendLockDays;

    /**
     * @notice The timestamp of the last work action performed.
     * @dev This can be used for tracking purposes and for enforcing time-based conditions.
     */
    uint256 public lastWorkTimestamp;

    /**
     * @notice A flag indicating whether the contract should check for an NFT.
     * @dev If set to true, the contract will check for the presence of an NFT during certain actions.
     */
    bool public isCheckNFT;

    /**
     * @notice The address of the NFT contract.
     * @dev This is the contract that manages the NFTs that may be required by this contract.
     */
    ERC721 public NFTAddress;

    /**
     * @notice Address where remaining bullets are collected.
     * @dev Bullets are a secondary token used in the ecosystem.
     */
    address public remainingBulletCollector;

    /**
     * @notice Precision loss prevention multiplier constant.
     * @dev This value is used for precision management during calculations.
     */
    uint256 private constant ACCU_REWARD_MULTIPLIER = 10**20;

    /**
     * @notice Struct defining the data for each option.
     * @dev This includes the addresses of the option, bullet, sniper, and the bullet balance.
     */
    struct OptionData {
        address optionAddress;
        address bullet;
        address sniper;
        uint256 bulletBalance;
    }

    /**
     * @notice Struct defining the user's data.
     * @dev This includes total staking amount, last entry time, and accumulated rewards for both uHODL and bHODL.
     */
    struct UserData {
        uint256 totalStakingAmount;
        uint256 uHODLEntryAccuReward;
        uint256 bHODLEntryAccuReward;
        uint256 lastEntryTime;
    }

    /**
     * @notice Struct defining the pool's data.
     * @dev This includes the total staking amount and accumulated rewards for both uHODL and bHODL.
     */
    struct PoolData {
        uint256 stakingAmount;
        uint256 uHODLAccuReward;
        uint256 bHODLAccuReward;
    }

    /**
     * @notice Struct defining the staking data for each staker.
     * @dev This includes the current and claimed staking amount, and the block height when these amounts were updated.
     */
    struct StakingData {
        uint256 claimStakingAmount;
        uint256 claimAmountUpdateBlockHeight;
        uint256 currentStakingAmount;
        uint256 stakingAmountUpdateBlockHeight;
    }

    /**
     * @notice Mapping to store user data for each address.
     * @dev Keys are user addresses and values are UserData struct.
     */
    mapping(address => UserData) public userDatas;

    /**
     * @notice Mapping to store staking data for each staker.
     * @dev Keys are user addresses and values are StakingData struct.
     */
    mapping(address => StakingData) public stakingInfo;

    /**
     * @notice Mapping to store claim information for each NFT.
     * @dev Keys are NFT IDs and values are amounts.
     */
    mapping(uint256 => uint256) public nftClaimInfo;

    /**
     * @notice Mapping to store claim information for each user.
     * @dev Keys are user addresses and values are amounts.
     */
    mapping(address => uint256) public userClaimInfo;

    /**
     * @notice The start block of the last delivery.
     * @dev This is used for tracking purposes.
     */
    uint256 public lastDeliverStartBlock;

    /**
     * @notice The end block of the last delivery.
     * @dev This is used for tracking purposes.
     */
    uint256 public lastDeliverEndBlock;

    /**
     * @notice The total daily share for bullet reward.
     * @dev This is the total share of all users who staked more than the bulletRewardThreshold in a day.
     */
    uint256 public dailyTotalShareBullet;

    /**
     * @notice The total daily share for bullet reward in the last period.
     * @dev This is the total share of all users who staked more than the bulletRewardThreshold in the last period.
     */
    uint256 public lastPeriodDailyTotalShareBullet;

    /**
     * @notice The total claim amount in the last period.
     * @dev This is the total claim amount of all users in the last period.
     */
    uint256 public lastPeriodDailyClaimTotal;

    /**
     * @notice The data of the pool.
     * @dev This includes the total staking amount and accumulated rewards for both uHODL and bHODL.
     */
    PoolData public poolData;

    /**
     * @notice The address of the uHODL rewarder contract.
     * @dev This contract is responsible for distributing rewards to uHODL stakers.
     */
    IStakingPoolRewarder public uHODLRewarder;

    /**
     * @notice The address of the bHODL rewarder contract.
     * @dev This contract is responsible for distributing rewards to bHODL stakers.
     */
    IStakingPoolRewarder public bHODLRewarder;

    /**
     * @notice Emitted when a user stakes DOB tokens.
     * @dev Includes the staker's address and the amount staked.
     * @param staker The address of the user that staked the tokens.
     * @param amount The amount of tokens staked.
     */
    event Staked(address indexed staker, uint256 amount);

    /**
     * @notice Emitted when a user unstakes DOB tokens.
     * @dev Includes the staker's address and the amount unstaked.
     * @param staker The address of the user that unstaked the tokens.
     * @param amount The amount of tokens unstaked.
     */
    event Unstaked(address indexed staker, uint256 amount);

    /**
     * @notice Emitted when the worker is changed.
     * @dev Includes the old and new worker addresses.
     * @param oldWorker The address of the old worker.
     * @param newWorker The address of the new worker.
     */
    event WorkerChanged(address oldWorker, address newWorker);

    /**
     * @notice Emitted when the factory is changed.
     * @dev Includes the old and new factory addresses.
     * @param oldFactory The address of the old factory.
     * @param newFactory The address of the new factory.
     */
    event FactoryChanged(address oldFactory, address newFactory);

    /**
     * @notice Emitted when the rewarder is changed.
     * @dev Includes the old and new rewarder addresses.
     * @param oldRewarder The address of the old rewarder.
     * @param newRewarder The address of the new rewarder.
     */
    event RewarderChanged(address oldRewarder, address newRewarder);

    /**
     * @notice Emitted when a reward is redeemed.
     * @dev Includes the staker's address, the rewarder's address, the amount, and the reward type.
     * @param staker The address of the user that redeemed the reward.
     * @param rewarder The address of the rewarder contract.
     * @param amount The amount of reward redeemed.
     * @param rewardType The type of reward (0 for uHODL, 1 for bHODL).
     */
    event RewardRedeemed(address indexed staker, address rewarder, uint256 amount, uint8 rewardType);

    /**
     * @notice Emitted when the bullet reward threshold is changed.
     * @dev Includes the old and new threshold values.
     * @param oldThreshold The old bullet reward threshold.
     * @param newThreshold The new bullet reward threshold.
     */
    event BulletRewardThresholdChanged(uint256 oldThreshold, uint256 newThreshold);

    /**
     * @notice Emitted when the extend lock days is changed.
     * @dev Includes the old and new lock days values.
     * @param oldDays The old extend lock days.
     * @param newDays The new extend lock days.
     */
    event ExtendLockDaysChanged(uint256 oldDays, uint256 newDays);

    /**
     * @notice Emitted when a bullet reward is given.
     * @dev Includes the user's address, the bullet's address, and the amount.
     * @param user The address of the user that received the reward.
     * @param bullet The address of the bullet token contract.
     * @param amount The amount of bullet reward given.
     */
    event BulletReward(address user, address bullet, uint256 amount);

    /**
     * @notice This modifier ensures only the worker can call the function.
     * @dev Reverts if the caller is not the worker.
     */
    modifier onlyWorker() {
        require(msg.sender == worker, "DOBStaking: caller is not the worker");
        _;
    }

    /**
     * @notice This modifier ensures only the option factory can call the function.
     * @dev Reverts if the caller is not the option factory.
     */
    modifier onlyFactory() {
        require(msg.sender == optionFactory, "DOBStaking: caller is not the option factory");
        _;
    }

    /**
     * @notice Initializes the DOBStakingPool contract with essential parameters.
     * @dev The initializer function is used in upgradeable contracts instead of a constructor.
     *      It checks that input addresses are not zero and then sets up initial contract state.
     * @param _feeCollector The address of the fee collector.
     * @param _bulletCollector The address of the bullet collector.
     * @param _rewardDispatcher The address of the reward dispatcher.
     * @param _uHODL The address of the uHODL contract.
     * @param _bHODL The address of the bHODL contract.
     * @param _DOB The address of the DOB token.
     */
    function __DOBStakingPool_init(
        address _feeCollector,
        address _bulletCollector,
        address _rewardDispatcher,
        address _uHODL,
        address _bHODL,
        address _DOB
    ) public initializer {
        require(_feeCollector != address(0), "DOBStakingPool: zero address");
        require(_bulletCollector != address(0), "DOBStakingPool: zero address");
        require(_rewardDispatcher != address(0), "DOBStakingPool: zero address");
        require(_uHODL != address(0), "DOBStakingPool: zero address");
        require(_bHODL != address(0), "DOBStakingPool: zero address");
        require(_DOB != address(0), "DOBStakingPool: zero address");

        __Ownable_init();
        __Pausable_init();
        __ReentrancyGuard_init();

        feeCollector = _feeCollector;
        bulletCollector = _bulletCollector;
        rewardDispatcher = _rewardDispatcher;
        uHODL = _uHODL;
        bHODL = _bHODL;
        DOB = _DOB;

        // Setting initial parameters
        bulletRewardThreshold = 1000e18;
        extendLockDays = 30 days;
        lastWorkTimestamp = block.timestamp;
    }

    /**
     * @notice Changes the worker address to a new one.
     * @dev Emits a WorkerChanged event after successfully changing the worker.
     * @param _worker The new worker's address.
     */
    function setWorker(address _worker) external onlyOwner {
        require(_worker != address(0), "DOBStakingPool: zero address");

        address oldWorker = worker;
        worker = _worker;

        emit WorkerChanged(oldWorker, worker);
    }

    /**
     * @notice Changes the factory address to a new one.
     * @dev Emits a FactoryChanged event after successfully changing the factory.
     * @param newFactory The new factory's address.
     */
    function setFactory(address newFactory) external onlyOwner {
        require(newFactory != address(0), "DOBStakingPool: zero address");

        address oldFactory = optionFactory;
        optionFactory = newFactory;

        emit FactoryChanged(oldFactory, optionFactory);
    }

    /**
     * @notice Changes the uHODL rewarder address to a new one.
     * @dev Emits a RewarderChanged event after successfully changing the rewarder.
     * @param _uHODLRewarder The new uHODL rewarder's address.
     */
    function setuHODLRewarder(address _uHODLRewarder) external onlyOwner {
        require(_uHODLRewarder != address(0), "DOBStakingPool: zero address");

        address olduHODLRewarder = address(_uHODLRewarder);
        uHODLRewarder = IStakingPoolRewarder(_uHODLRewarder);

        emit RewarderChanged(olduHODLRewarder, _uHODLRewarder);
    }

    /**
     * @notice Changes the bHODL rewarder address to a new one.
     * @dev Emits a RewarderChanged event after successfully changing the rewarder.
     * @param _bHODLRewarder The new bHODL rewarder's address.
     */
    function setbHODLRewarder(address _bHODLRewarder) external onlyOwner {
        require(_bHODLRewarder != address(0), "DOBStakingPool: zero address");

        address oldbHODLRewarder = address(_bHODLRewarder);
        bHODLRewarder = IStakingPoolRewarder(_bHODLRewarder);

        emit RewarderChanged(oldbHODLRewarder, _bHODLRewarder);
    }

    /**
     * @notice Changes the bullet reward threshold to a new one.
     * @dev Emits a BulletRewardThresholdChanged event after successfully changing the threshold.
     * @param _threshold The new bullet reward threshold.
     */
    function setBulletRewardThreshold(uint256 _threshold) external onlyOwner {
        require(_threshold > 0, "DOBStakingPool: zero threshold");

        uint256 oldThreshold = bulletRewardThreshold;
        bulletRewardThreshold = _threshold;

        emit BulletRewardThresholdChanged(oldThreshold, _threshold);
    }

    /**
     * @notice Changes the extend lock days to a new value.
     * @dev Emits an ExtendLockDaysChanged event after successfully changing the days.
     * @param _days The new extend lock days.
     */
    function setExtendLockDays(uint256 _days) external onlyOwner {
        require(_days > 0, "DOBStakingPool: zero days");

        uint256 oldDays = extendLockDays;
        extendLockDays = _days;

        emit ExtendLockDaysChanged(oldDays, _days);
    }

    /**
     * @notice Changes the fee collector address to a new one.
     * @param _feeCollector The new fee collector's address.
     */
    function setFeeCollector(address _feeCollector) external onlyOwner {
        require(_feeCollector != address(0), "DOBStakingPool: zero address");
        feeCollector = _feeCollector;
    }

    /**
     * @notice Changes the bullet collector address to a new one.
     * @param _bulletCollector The new bullet collector's address.
     */
    function setBulletCollector(address _bulletCollector) external onlyOwner {
        require(_bulletCollector != address(0), "DOBStakingPool: zero address");
        bulletCollector = _bulletCollector;
    }

    /**
     * @notice Changes the reward dispatcher address to a new one.
     * @param _rewardDispatcher The new reward dispatcher's address.
     */
    function setRewardDispatcher(address _rewardDispatcher) external onlyOwner {
        require(_rewardDispatcher != address(0), "DOBStakingPool: zero address");
        rewardDispatcher = _rewardDispatcher;
    }

    /**
     * @notice Changes the remaining bullet collector address to a new one.
     * @param _remainingBulletCollector The new remaining bullet collector's address.
     */
    function setRemainingBulletCollector(address _remainingBulletCollector) external onlyOwner {
        require(_remainingBulletCollector != address(0), "DOBStakingPool: zero address");
        remainingBulletCollector = _remainingBulletCollector;
    }

    /**
     * @notice Adds a new option to the activated options.
     * @dev Checks that none of the addresses are zero, and adds the option to the list of activated options.
     * @param _optionAddress The address of the new option.
     * @param _bulletAddress The address of the bullet for the new option.
     * @param _sniperAddress The address of the sniper for the new option.
     */
    function addOption(
        address _optionAddress,
        address _bulletAddress,
        address _sniperAddress
    ) external override onlyFactory {
        require(_optionAddress != address(0), "DOBStakingPool: zero address");
        require(_bulletAddress != address(0), "DOBStakingPool: zero address");
        require(_sniperAddress != address(0), "DOBStakingPool: zero address");

        uint256 bulletRewardAmount = IERC20Upgradeable(_bulletAddress).balanceOf(bulletCollector);
        activatedOptions.push(OptionData(_optionAddress, _bulletAddress, _sniperAddress, bulletRewardAmount));
    }

    /**
     * @notice Removes an option from the activated options.
     * @dev Iterates over the activated options, replaces the option to remove with the last element in the array, and removes the last element.
     * @param _optionAddress The address of the option to remove.
     */
    function removeOption(address _optionAddress) external override onlyFactory {
        require(_optionAddress != address(0), "DOBStakingPool: zero address");

        for (uint8 i = 0; i < activatedOptions.length; i++) {
            if (activatedOptions[i].optionAddress == _optionAddress) {
                activatedOptions[i] = activatedOptions[activatedOptions.length - 1];
                activatedOptions.pop();
                break;
            }
        }
    }

    /**
     * @notice Updates the list of activated options.
     * @dev Iterates over the activated options and removes those that have expired.
     */
    function updateActivatedOptions() internal {
        OptionData[] memory oldActivatedOptions = activatedOptions;
        delete activatedOptions;

        for (uint8 i = 0; i < oldActivatedOptions.length; i++) {
            uint256 expiryTime = IOption(oldActivatedOptions[i].optionAddress).getExpiryTime();
            if (block.timestamp <= expiryTime) {
                uint256 bulletRewardAmount = IERC20Upgradeable(oldActivatedOptions[i].bullet).balanceOf(bulletCollector);
                oldActivatedOptions[i].bulletBalance = bulletRewardAmount;
                activatedOptions.push(oldActivatedOptions[i]);
            }
        }
    }

    /**
     * @notice Returns the number of currently activated options.
     * @dev Checks the length property of the activatedOptions array.
     * @return A uint256 representing the number of activated options.
     */
    function activatedOptionLength() external view returns (uint256) {
        return activatedOptions.length;
    }

    /**
     * @notice This function allows a user to stake their DOB tokens.
     * @dev
     * - Ensures that the pool is not paused and that the function is not called within the same block the worker is updating.
     * - Calculates the pending uHODL and bHODL rewards for the user.
     * - Increases the user's total staking amount and the pool's staking amount by the amount staked.
     * - Updates the uHODL and bHODL entry rewards.
     * - Transfers the staked DOB tokens from the user to the contract.
     * - Settles any pending rewards with vesting.
     * - Updates the daily staking amount for bullet rewards.
     * @param amount The amount of DOB tokens to stake.
     */
    function stake(uint256 amount) external whenNotPaused nonReentrant {
        require(amount > 0, "DOBStaking: cannot stake zero amount");
        require(block.number != lastDeliverEndBlock, "DOBStaking: worker is updating in same block!");

        _accuHodlReward();
        uint256 uHODLRewardToVest = poolData
        .uHODLAccuReward
        .sub(userDatas[msg.sender].uHODLEntryAccuReward)
        .mul(userDatas[msg.sender].totalStakingAmount)
        .div(ACCU_REWARD_MULTIPLIER);
        uint256 bHODLRewardToVest = poolData
        .bHODLAccuReward
        .sub(userDatas[msg.sender].bHODLEntryAccuReward)
        .mul(userDatas[msg.sender].totalStakingAmount)
        .div(ACCU_REWARD_MULTIPLIER);

        userDatas[msg.sender].totalStakingAmount = userDatas[msg.sender].totalStakingAmount.add(amount);
        poolData.stakingAmount = poolData.stakingAmount.add(amount);

        userDatas[msg.sender].uHODLEntryAccuReward = poolData.uHODLAccuReward;
        userDatas[msg.sender].bHODLEntryAccuReward = poolData.bHODLAccuReward;

        SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(DOB), msg.sender, address(this), amount);

        uHODLRewarder.onReward(1, msg.sender, uHODLRewardToVest, userDatas[msg.sender].lastEntryTime);
        bHODLRewarder.onReward(1, msg.sender, bHODLRewardToVest, userDatas[msg.sender].lastEntryTime);

        userDatas[msg.sender].lastEntryTime = block.timestamp;

        uint256 oldDailyStakingAmount = 0;
        if (
            stakingInfo[msg.sender].stakingAmountUpdateBlockHeight > lastDeliverEndBlock ||
            stakingInfo[msg.sender].currentStakingAmount < bulletRewardThreshold
        ) {
            oldDailyStakingAmount = stakingInfo[msg.sender].currentStakingAmount;
            stakingInfo[msg.sender].currentStakingAmount = stakingInfo[msg.sender].currentStakingAmount.add(amount);
        } else {
            stakingInfo[msg.sender].claimStakingAmount = stakingInfo[msg.sender].currentStakingAmount;
            stakingInfo[msg.sender].claimAmountUpdateBlockHeight = stakingInfo[msg.sender].stakingAmountUpdateBlockHeight;
            stakingInfo[msg.sender].currentStakingAmount = amount;
        }
        stakingInfo[msg.sender].stakingAmountUpdateBlockHeight = block.number;
        if (oldDailyStakingAmount >= bulletRewardThreshold) {
            dailyTotalShareBullet = dailyTotalShareBullet.add(amount);
        } else if (stakingInfo[msg.sender].currentStakingAmount >= bulletRewardThreshold) {
            dailyTotalShareBullet = dailyTotalShareBullet.add(stakingInfo[msg.sender].currentStakingAmount);
        }

        emit Staked(msg.sender, amount);
    }

    /**
     * @notice This function allows a user to unstake their DOB tokens.
     * @dev
     * - Checks that the pool is not paused, that the function is not called within the same block the worker is updating, and that the user's tokens have been staked for at least `extendLockDays`.
     * - Calculates the pending uHODL and bHODL rewards for the user.
     * - Decreases the user's total staking amount and the pool's staking amount by the amount unstaked.
     * - Updates the uHODL and bHODL entry rewards.
     * - Transfers the unstaked DOB tokens from the contract to the user.
     * - Settles any pending rewards with vesting.
     * - Updates the daily staking amount for bullet rewards.
     * @param amount The amount of DOB tokens to unstake.
     */
    function unstake(uint256 amount) external whenNotPaused nonReentrant {
        require(
            block.timestamp >= userDatas[msg.sender].lastEntryTime + extendLockDays,
            "DOBStaking: Less than unlock time"
        );
        require(block.number != lastDeliverEndBlock, "DOBStaking: worker is updating in same block!");

        _accuHodlReward();
        uint256 uHODLRewardToVest = poolData
        .uHODLAccuReward
        .sub(userDatas[msg.sender].uHODLEntryAccuReward)
        .mul(userDatas[msg.sender].totalStakingAmount)
        .div(ACCU_REWARD_MULTIPLIER);
        uint256 bHODLRewardToVest = poolData
        .bHODLAccuReward
        .sub(userDatas[msg.sender].bHODLEntryAccuReward)
        .mul(userDatas[msg.sender].totalStakingAmount)
        .div(ACCU_REWARD_MULTIPLIER);

        userDatas[msg.sender].totalStakingAmount = userDatas[msg.sender].totalStakingAmount.sub(amount);
        poolData.stakingAmount = poolData.stakingAmount.sub(amount);

        userDatas[msg.sender].uHODLEntryAccuReward = poolData.uHODLAccuReward;
        userDatas[msg.sender].bHODLEntryAccuReward = poolData.bHODLAccuReward;

        SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(DOB), msg.sender, amount);

        uHODLRewarder.onReward(1, msg.sender, uHODLRewardToVest, userDatas[msg.sender].lastEntryTime);
        bHODLRewarder.onReward(1, msg.sender, bHODLRewardToVest, userDatas[msg.sender].lastEntryTime);

        if (
            stakingInfo[msg.sender].stakingAmountUpdateBlockHeight > lastDeliverEndBlock ||
            stakingInfo[msg.sender].currentStakingAmount < bulletRewardThreshold
        ) {
            uint256 oldDailyStakingAmount = stakingInfo[msg.sender].currentStakingAmount;

            if (stakingInfo[msg.sender].currentStakingAmount < amount) {
                stakingInfo[msg.sender].currentStakingAmount = 0;
            } else {
                stakingInfo[msg.sender].currentStakingAmount = stakingInfo[msg.sender].currentStakingAmount.sub(amount);
            }
            if (oldDailyStakingAmount >= bulletRewardThreshold) {
                if (stakingInfo[msg.sender].currentStakingAmount < bulletRewardThreshold) {
                    dailyTotalShareBullet = dailyTotalShareBullet.sub(oldDailyStakingAmount);
                } else {
                    dailyTotalShareBullet = dailyTotalShareBullet.sub(amount);
                }
            }
        } else {
            stakingInfo[msg.sender].claimStakingAmount = stakingInfo[msg.sender].currentStakingAmount;
            stakingInfo[msg.sender].claimAmountUpdateBlockHeight = stakingInfo[msg.sender].stakingAmountUpdateBlockHeight;
            stakingInfo[msg.sender].currentStakingAmount = 0;
        }
        stakingInfo[msg.sender].stakingAmountUpdateBlockHeight = block.number;

        emit Unstaked(msg.sender, amount);
    }

    /**
     * @notice This function allows the owner to enable or disable the NFT check, and to set the NFT contract address
     *          and the remaining bullet collector address.
     * @dev
     * - Checks that the provided NFT contract address and remaining bullet collector address are valid if `_isCheckNFT` is true.
     * - Sets `isCheckNFT`, `NFTAddress`, and `remainingBulletCollector` according to the provided arguments.
     * @param _isCheckNFT If true, the NFT check will be enabled.
     * @param _nftAddress The address of the NFT contract.
     * @param _remainingBulletCollector The address of the remaining bullet collector.
     */
    function setIsCheckNFT(
        bool _isCheckNFT,
        ERC721 _nftAddress,
        address _remainingBulletCollector
    ) public onlyOwner {
        if (_isCheckNFT) {
            require(address(_nftAddress) != address(0), "DOBStaking: NFT zero address");
            require(_remainingBulletCollector != address(0), "DOBStaking: surplus bullet collector zero address");
            uint256 size;
            assembly {
                size := extcodesize(_nftAddress)
            }
            require(size > 0, "Not a contract");
            NFTAddress = _nftAddress;
            remainingBulletCollector = _remainingBulletCollector;
        }
        isCheckNFT = _isCheckNFT;
    }

    /**
     * @notice This function checks if an NFT belonging to a user has already claimed rewards or not.
     * @dev
     * - Checks if `isCheckNFT` is true. If so, it requires that the provided NFT hasn't already claimed rewards.
     * - Checks if the owner of the NFT is the user. If these conditions are met, it returns true. Otherwise, it returns false.
     * @param _userAddress The address of the user to check.
     * @param _tokenId The ID of the NFT to check.
     * @return bool Returns true if the NFT check is successful, false otherwise.
     */

    function nftCheckSuccess(address _userAddress, uint256 _tokenId) private view returns (bool) {
        if (isCheckNFT) {
            require(nftClaimInfo[_tokenId] <= lastDeliverEndBlock, "DOBStaking:this nft has already claimed for rewards");
            if (NFTAddress.ownerOf(_tokenId) == _userAddress) {
                return true;
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

    /**
     * @notice Handles daily tasks for the smart contract.
     * @dev
     * - Can only be called by an account with the "Worker" role.
     * - Only executable when the contract is not paused.
     * - Ensures the last work has been done more than 18 hours ago.
     * - Loops over activated options, checks balance, makes transfers, and updates the contract's internal state.
     * @param _balanceCheck The boolean flag to check the balance.
     */
    function dailyWork(bool _balanceCheck) external onlyWorker whenNotPaused nonReentrant {
        require(block.timestamp > (lastWorkTimestamp + 18 hours), "DOBStaking: last work not over 18 hours");

        uint256 remainingAmount = lastPeriodDailyTotalShareBullet.sub(lastPeriodDailyClaimTotal);
        if (remainingAmount > 0 && lastPeriodDailyTotalShareBullet > 0) {
            for (uint8 j = 0; j < activatedOptions.length; j++) {
                if (activatedOptions[j].bulletBalance > 0) {
                    uint256 bulletAmount = activatedOptions[j].bulletBalance.mul(remainingAmount).div(
                        lastPeriodDailyTotalShareBullet
                    );

                    if (
                        _balanceCheck &&
                        IERC20Upgradeable(activatedOptions[j].bullet).balanceOf(bulletCollector) < bulletAmount
                    ) {
                        continue;
                    }

                    if (bulletAmount <= activatedOptions[j].bulletBalance) {
                        ITokenKeeper(bulletCollector).transferToken(
                            activatedOptions[j].bullet,
                            remainingBulletCollector,
                            bulletAmount
                        );
                        emit BulletReward(remainingBulletCollector, activatedOptions[j].bullet, bulletAmount);
                    }
                }
            }
        }

        updateActivatedOptions();
        lastPeriodDailyTotalShareBullet = dailyTotalShareBullet;
        lastPeriodDailyClaimTotal = 0;
        dailyTotalShareBullet = 0;
        lastDeliverStartBlock = lastDeliverEndBlock;
        lastDeliverEndBlock = block.number;
        lastWorkTimestamp = block.timestamp;
    }

    /**
     * @notice Draw BULLET rewards for a user.
     * @dev
     * - Requires the user hasn't claimed for rewards multiple times in one day.
     * - Checks that the user owns the NFT.
     * - Ensures there are some BULLET rewards left to claim.
     * - Checks the user's staking status and gives the BULLET rewards accordingly.
     * @param user The address of the user.
     * @param tokenID The id of the NFT.
     */
    function drawReward(address user, uint256 tokenID) external whenNotPaused nonReentrant {
        require(userClaimInfo[user] <= lastDeliverEndBlock, "DOBStaking: This user has already claimed for rewards");
        require(nftCheckSuccess(user, tokenID), "DOBStaking: You do not have the NFT");
        require(lastPeriodDailyTotalShareBullet > 0, "DOBStaking: lastPeriodDailyTotalShareBullet is zero");

        uint256 shareAmount = 0;
        if (
            stakingInfo[user].currentStakingAmount >= bulletRewardThreshold &&
            stakingInfo[user].stakingAmountUpdateBlockHeight > lastDeliverStartBlock &&
            stakingInfo[user].stakingAmountUpdateBlockHeight <= lastDeliverEndBlock
        ) {
            shareAmount = stakingInfo[user].currentStakingAmount;
        }
        if (
            stakingInfo[user].claimStakingAmount >= bulletRewardThreshold &&
            stakingInfo[user].claimAmountUpdateBlockHeight > lastDeliverStartBlock &&
            stakingInfo[user].claimAmountUpdateBlockHeight <= lastDeliverEndBlock
        ) {
            shareAmount = stakingInfo[user].claimStakingAmount;
        }

        require(shareAmount > 0, "DOBStaking: You do not have reward to claim");

        lastPeriodDailyClaimTotal += shareAmount;

        require(
            lastPeriodDailyClaimTotal <= lastPeriodDailyTotalShareBullet,
            "DOBStaking: claim total is large than share total"
        );

        for (uint8 j = 0; j < activatedOptions.length; j++) {
            uint256 bulletAmount = activatedOptions[j].bulletBalance.mul(shareAmount).div(lastPeriodDailyTotalShareBullet);
            if (bulletAmount > 0 && bulletAmount <= activatedOptions[j].bulletBalance) {
                ITokenKeeper(bulletCollector).transferToken(activatedOptions[j].bullet, user, bulletAmount);
                emit BulletReward(user, activatedOptions[j].bullet, bulletAmount);
            }
        }

        nftClaimInfo[tokenID] = block.number;
        userClaimInfo[user] = block.number;
    }

    /**
     * @notice Calculates and returns the total accumulated HODL rewards for a user.
     * @dev
     * - The total accumulated rewards for a user is the sum of pending rewards (rewards yet to be claimed) and rewards that are already vested and vesting.
     * - This function checks the pool's staking amount. If greater than zero, it calculates the amount of rewards for each staking token, and updates the real accumulated rewards.
     * - Then, it calculates the pending rewards, and sums them up with the rewards in the rewarder contract, which includes both vested and vesting rewards.
     * @param user The address of the user.
     * @return uHODLReward The total uHODL reward for the user.
     * @return bHODLReward The total bHODL reward for the user.
     */
    function getReward(address user) external view returns (uint256 uHODLReward, uint256 bHODLReward) {
        uint256 realuHODLAccuReward = poolData.uHODLAccuReward;
        uint256 realbHODLAccuReward = poolData.bHODLAccuReward;
        if (poolData.stakingAmount > 0) {
            uint256 uAmountForReward = IERC20Upgradeable(uHODL).balanceOf(feeCollector);
            uint256 bAmountForReward = IERC20Upgradeable(bHODL).balanceOf(feeCollector);

            realuHODLAccuReward = uAmountForReward.mul(ACCU_REWARD_MULTIPLIER).div(poolData.stakingAmount).add(
                realuHODLAccuReward
            );
            realbHODLAccuReward = bAmountForReward.mul(ACCU_REWARD_MULTIPLIER).div(poolData.stakingAmount).add(
                realbHODLAccuReward
            );
        }

        uint256 uHODLpendingReward = realuHODLAccuReward
        .sub(userDatas[user].uHODLEntryAccuReward)
        .mul(userDatas[user].totalStakingAmount)
        .div(ACCU_REWARD_MULTIPLIER);
        uint256 bHODlpendingReward = realbHODLAccuReward
        .sub(userDatas[user].bHODLEntryAccuReward)
        .mul(userDatas[user].totalStakingAmount)
        .div(ACCU_REWARD_MULTIPLIER);

        uint256 uHODLRewardInRewarder = uHODLRewarder.calculateTotalReward(user, 1);
        uint256 bHODLRewardInRewarder = bHODLRewarder.calculateTotalReward(user, 1);

        uHODLReward = uHODLpendingReward.add(uHODLRewardInRewarder);
        bHODLReward = bHODlpendingReward.add(bHODLRewardInRewarder);
    }

    /**
     * @notice Allows the user to claim their HODL rewards.
     * @dev This function calculates the pending reward for a user, settles these rewards to the rewarder contract with vesting, and then attempts to claim withdrawable rewards from the rewarder.
     * - It requires that the claimable rewards are more than zero and emits a RewardRedeemed event for each token reward that is claimed.
     */
    function redeemReward() external nonReentrant whenNotPaused {
        _accuHodlReward();
        uint256 uHODLRewardToVest = poolData
        .uHODLAccuReward
        .sub(userDatas[msg.sender].uHODLEntryAccuReward)
        .mul(userDatas[msg.sender].totalStakingAmount)
        .div(ACCU_REWARD_MULTIPLIER);
        uint256 bHODLRewardToVest = poolData
        .bHODLAccuReward
        .sub(userDatas[msg.sender].bHODLEntryAccuReward)
        .mul(userDatas[msg.sender].totalStakingAmount)
        .div(ACCU_REWARD_MULTIPLIER);

        uHODLRewarder.onReward(1, msg.sender, uHODLRewardToVest, userDatas[msg.sender].lastEntryTime);
        userDatas[msg.sender].uHODLEntryAccuReward = poolData.uHODLAccuReward;
        bHODLRewarder.onReward(1, msg.sender, bHODLRewardToVest, userDatas[msg.sender].lastEntryTime);
        userDatas[msg.sender].bHODLEntryAccuReward = poolData.bHODLAccuReward;

        uint256 uHODLClaimable = uHODLRewarder.calculateWithdrawableReward(msg.sender, 1);
        uint256 bHODLClaimable = bHODLRewarder.calculateWithdrawableReward(msg.sender, 1);
        require(uHODLClaimable > 0 || bHODLClaimable > 0, "DOBStaking: haven't withdrawable reward");
        if (uHODLClaimable > 0) {
            uint256 claimed = uHODLRewarder.claimVestedReward(1, msg.sender);
            emit RewardRedeemed(msg.sender, address(uHODLRewarder), claimed, 0);
        }

        if (bHODLClaimable > 0) {
            uint256 claimed = bHODLRewarder.claimVestedReward(1, msg.sender);
            emit RewardRedeemed(msg.sender, address(bHODLRewarder), claimed, 1);
        }
    }

    /**
     * @notice Internal function that accumulates the HODL reward.
     * @dev This function checks the pool's staking amount, if it is greater than zero, it transfers the reward amount from the fee collector to the reward dispatcher and updates the accumulated rewards.
     */
    function _accuHodlReward() internal {
        if (poolData.stakingAmount > 0) {
            uint256 uAmountForReward = IERC20Upgradeable(uHODL).balanceOf(feeCollector);
            uint256 bAmountForReward = IERC20Upgradeable(bHODL).balanceOf(feeCollector);
            if (uAmountForReward > 0) {
                SafeERC20Upgradeable.safeTransferFrom(
                    IERC20Upgradeable(uHODL),
                    feeCollector,
                    rewardDispatcher,
                    uAmountForReward
                );
            }
            if (bAmountForReward > 0) {
                SafeERC20Upgradeable.safeTransferFrom(
                    IERC20Upgradeable(bHODL),
                    feeCollector,
                    rewardDispatcher,
                    bAmountForReward
                );
            }
            poolData.uHODLAccuReward = uAmountForReward.mul(ACCU_REWARD_MULTIPLIER).div(poolData.stakingAmount).add(
                poolData.uHODLAccuReward
            );
            poolData.bHODLAccuReward = bAmountForReward.mul(ACCU_REWARD_MULTIPLIER).div(poolData.stakingAmount).add(
                poolData.bHODLAccuReward
            );
        }
    }

    /**
     * @notice Allows a user to unstake their tokens in case of an emergency.
     * @dev This function can only be called when the contract is paused. It updates the pool's total staking amount and transfers the user's staking amount back to the user.
     */
    function emergencyUnstake() external whenPaused {
        require(userDatas[msg.sender].totalStakingAmount > 0, "DOBStaking: total staking amount is zero");
        uint256 amount = userDatas[msg.sender].totalStakingAmount;
        userDatas[msg.sender].totalStakingAmount = 0;
        poolData.stakingAmount = poolData.stakingAmount.sub(amount);

        SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(DOB), msg.sender, amount);
        emit Unstaked(msg.sender, amount);
    }

    /**
     * @notice Pauses the contract, preventing certain actions until unpaused.
     * @dev Only callable by the owner of the contract.
     */
    function pause() external onlyOwner {
        _pause();
    }
}

// File: @openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    function __Ownable_init() internal initializer {
        __Context_init_unchained();
        __Ownable_init_unchained();
    }

    function __Ownable_init_unchained() internal initializer {
        _setOwner(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _setOwner(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _setOwner(newOwner);
    }

    function _setOwner(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
    uint256[49] private __gap;
}

// File: @openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library MathUpgradeable {
    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a / b + (a % b == 0 ? 0 : 1);
    }
}

// File: @openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler
 * now has built in overflow checking.
 */
library SafeMathUpgradeable {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

// File: @openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol



// SPDX-License-Identifier: MIT

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

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    function __ReentrancyGuard_init() internal initializer {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal initializer {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
    uint256[49] private __gap;
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
    using Address for address;
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to owner address
    mapping(uint256 => address) private _owners;

    // Mapping owner address to token count
    mapping(address => uint256) private _balances;

    // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overriden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not owner nor approved for all"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        require(_exists(tokenId), "ERC721: approved query for nonexistent token");

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        require(operator != _msgSender(), "ERC721: approve to caller");

        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        _safeTransfer(from, to, tokenId, _data);
    }

    /**
     * @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.
     *
     * `_data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, _data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits a {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param _data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}

// File: contracts/interfaces/IStakingPoolRewarder.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title IStakingPoolRewarder interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for calculating and distributing staking pool rewards
 */
interface IStakingPoolRewarder {
    /**
     * @notice Calculate the total amount of reward tokens for the specified user and pool ID
     * @dev Calculates the total amount of reward tokens for the specified user and pool ID
     * @param user The address of the user to calculate rewards for
     * @param poolId The ID of the staking pool to calculate rewards for
     * @return The total amount of reward tokens for the specified user and pool ID
     */
    function calculateTotalReward(address user, uint256 poolId) external view returns (uint256);

    /**
     * @notice Calculate the amount of reward tokens that can be withdrawn by the specified user and pool ID
     * @dev Calculates the amount of reward tokens that can be withdrawn by the specified user and pool ID
     * @param user The address of the user to calculate rewards for
     * @param poolId The ID of the staking pool to calculate rewards for
     * @return The amount of reward tokens that can be withdrawn by the specified user and pool ID
     */
    function calculateWithdrawableReward(address user, uint256 poolId) external view returns (uint256);

    /**
     * @notice Update the vesting schedule and claimable amounts for the specified user and pool ID
     * @dev Calculates and updates the user's vested and unvested token amounts based on their staking activity, and adds any vested tokens to the user's claimable amounts.
     * @param poolId The ID of the staking pool to update vesting schedule and claimable amounts for
     * @param user The address of the user to update vesting schedule and claimable amounts for
     * @param amount The amount of reward tokens earned by the user
     * @param entryTime The timestamp of the user's entry into the staking pool
     */
    function onReward(uint256 poolId, address user, uint256 amount, uint256 entryTime) external;

    /**
     * @notice Claim vested reward tokens for the specified user and pool ID
     * @dev Claims vested reward tokens for the specified user and pool ID
     * @param poolId The ID of the staking pool to claim rewards from
     * @param user The address of the user to claim rewards for
     * @return The amount of vested reward tokens claimed by the specified user and pool ID
     */
    function claimVestedReward(uint256 poolId, address user) external returns (uint256);
}

// File: contracts/interfaces/IDOBStakingPool.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title IDOBStakingPool interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for managing staking pools for DOB options
 */
interface IDOBStakingPool {
    /**
     * @notice Add an option to the staking pool
     * @dev Adds the specified option to the staking pool and associates it with the specified Bullet and Sniper tokens
     * @param _optionAddress The address of the option contract to add
     * @param _bulletAddress The address of the associated BULLET token contract
     * @param _sniperAddress The address of the associated SNIPER token contract
     */
    function addOption(address _optionAddress, address _bulletAddress, address _sniperAddress) external;

    /**
     * @notice Remove an option from the staking pool
     * @dev Removes the specified option from the staking pool
     * @param _optionAddress The address of the option contract to remove
     */
    function removeOption(address _optionAddress) external;
}

// File: contracts/interfaces/IOption.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title IOption interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for managing options contracts
 */
interface IOption {
    /**
     * @notice Get the expiry time of the option
     * @dev Returns the expiry time of the option in seconds since the Unix epoch
     * @return The expiry time of the option
     */
    function getExpiryTime() external view returns (uint256);

    /**
     * @notice Initialize the option contract with the specified parameters
     * @dev Initializes the option contract with the specified strike price, exercise timestamp, and option type
     * @param _strikePrice The strike price of the option
     * @param _exerciseTimestamp The exercise timestamp of the option
     * @param _type The type of the option (i.e., call or put)
     */
    function initialize(uint256 _strikePrice, uint256 _exerciseTimestamp, uint8 _type) external;

    /**
     * @notice Set up the option contract with the specified parameters
     * @dev Sets up the option contract with the specified option ID, start block, uHODL and bHODL token addresses, fund address, and Bullet and Sniper token addresses
     * @param _optionID The ID of the option contract
     * @param _startBlock The start block of the option contract
     * @param _uHODLAddress The address of the uHODL token contract
     * @param _bHODLTokenAddress The address of the bHODL token contract
     * @param _fund The address of the fund contract
     * @param _bullet The address of the BULLET token contract
     * @param _sniper The address of the SNIPER token contract
     */
    function setup(
        uint256 _optionID,
        uint256 _startBlock,
        address _uHODLAddress,
        address _bHODLTokenAddress,
        address _fund,
        address _bullet,
        address _sniper
    ) external;

    /**
     * @notice Update the strike price of the option
     * @dev Updates the strike price of the option to the specified value
     * @param _strikePrice The new strike price of the option
     */
    function updateStrike(uint256 _strikePrice) external;

    /**
     * @notice Set all fee and reward ratios for the option contract
     * @dev Sets all fee and reward ratios for the option contract to the specified values
     * @param _entryFeeRatio The entry fee ratio to set in basis points
     * @param _exerciseFeeRatio The exercise fee ratio to set in basis points
     * @param _withdrawFeeRatio The withdraw fee ratio to set in basis points
     * @param _redeemFeeRatio The redeem fee ratio to set in basis points
     * @param _bulletToRewardRatio The BULLET-to-reward ratio to in base 100
     */
    function setAllRatio(
        uint16 _entryFeeRatio,
        uint16 _exerciseFeeRatio,
        uint16 _withdrawFeeRatio,
        uint16 _redeemFeeRatio,
        uint16 _bulletToRewardRatio
    ) external;

    /**
     * @notice Set the entry fee ratio for the option contract
     * @dev Sets the entry fee ratio for the option contract to the specified value
     * @param _feeRatio The entry fee ratio to set
     */
    function setOptionEntryFeeRatio(uint16 _feeRatio) external;

    /**
     * @notice Set the exercise fee ratio for the option contract
     * @dev Sets the exercise fee ratio for the option contract to the specified value
     * @param _feeRatio The exercise fee ratio to set
     */
    function setOptionExerciseFeeRatio(uint16 _feeRatio) external;

    /**
     * @notice Set the withdraw fee ratio for the option contract
     * @dev Sets the withdraw fee ratio for the option contract to the specified value
     * @param _feeRatio The withdraw fee ratio to set
     */
    function setOptionWithdrawFeeRatio(uint16 _feeRatio) external;

    /**
     * @notice Set the redeem fee ratio for the option contract
     * @dev Sets the redeem fee ratio for the option contract to the specified value
     * @param _feeRatio The redeem fee ratio to set
     */
    function setOptionRedeemFeeRatio(uint16 _feeRatio) external;

    /**
     * @notice Set the BULLET-to-reward ratio for the option contract
     * @dev Sets the BULLET-to-reward ratio for the option contract to the specified value
     * @param _feeRatio The BULLET-to-reward ratio to set
     */
    function setOptionBulletToRewardRatio(uint16 _feeRatio) external;

    /**
     * @notice Set the fund address for the option contract
     * @dev Sets the fund address for the option contract to the specified value
     * @param _fund The fund address to set
     */
    function setFund(address _fund) external;

    /**
     * @notice Exits the option by unstaking and redeeming all rewards.
     * @dev This function unstakes the user's tokens, redeems their SNIPER tokens, and withdraws their rewards.
     */
    function exitAll() external;

    /**
     * @notice Enters an options contract by depositing a certain amount of tokens.
     * @dev This function is used to enter an options contract. The sender should have approved the transfer.
     *      The amount of tokens is transferred to this contract, the entry fee is calculated, distributed,
     *      and subtracted from the amount. The remaining amount is used to mint BULLET and SNIPER tokens,
     *      which are passed to the fund and the staking pool, respectively.
     * @param _amount The amount of tokens to enter.
     */
    function enter(uint256 _amount) external;

    /**
     * @notice Exercises the option by burning option tokens and receiving base tokens.
     * @dev This function burns a specific amount of BULLET tokens and calculates the amount of base tokens
     *      to transfer depending on the option type (call or put). It also calculates and applies the exercise fee.
     * @param _targetAmount The amount of option tokens to exercise.
     */
    function exercise(uint256 _targetAmount) external;

    /**
     * @notice Unwinds a specific amount of options.
     * @dev This funciton burns the user's SNIPER and BULLET for the option to withdraw collateral.
     * @param _unwindAmount The amount of options to unwind.
     */
    function unwind(uint256 _unwindAmount) external;
}

// File: contracts/interfaces/ITokenKeeper.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title ITokenKeeper interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for managing TokenKeeper contracts
 */
interface ITokenKeeper {
    /**
     * @notice Transfers a certain amount of an ERC20 token to a recipient.
     * @dev Transfers an ERC20 token from the TokenKeeper contract to a recipient. Only the contract owner or a whitelisted contract can call this function, and only if transfers are not frozen.
     * @param _tokenAddress The address of the ERC20 token to be transferred.
     * @param _receiver The address to receive the tokens.
     * @param _amount The amount of tokens to be transferred.
     */
    function transferToken(
        address _tokenAddress,
        address _receiver,
        uint256 _amount
    ) external;

    /**
     * @notice Approves a spender to spend a certain amount of an ERC20 token.
     * @dev Approves a spender to spend an ERC20 token on behalf of the TokenKeeper contract. Only the contract owner or a whitelisted contract can call this function, and only if transfers are not frozen.
     * @param _token The address of the ERC20 token.
     * @param _spender The address to be approved as a spender.
     * @param _approveAmount The amount of tokens the spender is approved to spend.
     */
    function approveToken(
        address _token,
        address _spender,
        uint256 _approveAmount
    ) external;
}

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



// SPDX-License-Identifier: MIT

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`, 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 be 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: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * 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 Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @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 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);

    /**
     * @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;
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// File: @openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

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

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @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
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 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 functionCall(target, data, "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");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(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) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(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) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason 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 {
            // 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

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

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

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }
}

// File: @openzeppelin/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT

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: contracts/token/TokenKeeper.sol



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

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "../interfaces/ITokenKeeper.sol";

/**
 * @title TokenKeeper
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice A contract for managing ERC20 token transfers and approvals.
 * @dev The TokenKeeper contract holds a list of whitelisted contracts which are authorized to call the transfer and approve functions.
 *      Only the owner of the contract can add or remove contracts from the whitelist. The owner can also freeze all transfers.
 */
contract TokenKeeper is OwnableUpgradeable, ITokenKeeper, ReentrancyGuardUpgradeable {
    using SafeMathUpgradeable for uint256;

    /**
     * @dev Mapping that keeps track of whitelisted contracts. Contracts in this list are allowed to call the transferToken and approveToken functions.
     */
    mapping(address => bool) public whitelistContract;

    /**
     * @dev A boolean variable to indicate if all transfers are frozen.
     */
    bool public isTransferFrozen;

    /**
     * @dev Modifier that allows only the owner or whitelisted contracts to call certain functions.
     */
    modifier onlyOwnerOrWhitelistContract() {
        require(
            whitelistContract[msg.sender] || msg.sender == owner(),
            "TokenKeeper: caller is not owner or whitelist contract"
        );
        _;
    }

    /**
     * @dev Modifier that allows function execution only if transfers are not frozen.
     */
    modifier notFrozen() {
        require(!isTransferFrozen, "TokenKeeper: transfer is frozen");
        _;
    }

    /**
     * @notice Initializes the TokenKeeper contract.
     * @dev Initializes the contract with Ownable upgradeability and sets the isTransferFrozen state to false.
     */
    function __TokenKeeper_init() public initializer {
        __Ownable_init();
        __ReentrancyGuard_init();
        isTransferFrozen = false;
    }

    /**
     * @notice Sets the whitelist status for a contract.
     * @dev Only the contract owner can set the whitelist status of a contract.
     * @param _whitelistContract The address of the contract to be whitelisted or removed from the whitelist.
     * @param _isActive Whether the contract should be whitelisted.
     */
    function setWhitelistContract(address _whitelistContract, bool _isActive) external onlyOwner {
        require(_whitelistContract != address(0), "TokenKeeper: zero address");
        whitelistContract[_whitelistContract] = _isActive;
    }

    /**
     * @notice Sets the transfer frozen status.
     * @dev Only the contract owner can freeze or unfreeze all transfers.
     * @param _isTransferFrozen The new frozen status.
     */
    function setIsTransferFrozen(bool _isTransferFrozen) external onlyOwner {
        isTransferFrozen = _isTransferFrozen;
    }

    /**
     * @notice Transfers a certain amount of an ERC20 token to a recipient.
     * @dev Transfers an ERC20 token from the TokenKeeper contract to a recipient. Only the contract owner or a whitelisted contract can call this function, and only if transfers are not frozen.
     * @param _tokenAddress The address of the ERC20 token to be transferred.
     * @param _receiver The address to receive the tokens.
     * @param _amount The amount of tokens to be transferred.
     */
    function transferToken(
        address _tokenAddress,
        address _receiver,
        uint256 _amount
    ) external override notFrozen onlyOwnerOrWhitelistContract nonReentrant {
        require(_tokenAddress != address(0), "TokenKeeper: zero address");

        IERC20(_tokenAddress).transfer(_receiver, _amount);
    }

    /**
     * @notice Approves a spender to spend a certain amount of an ERC20 token.
     * @dev Approves a spender to spend an ERC20 token on behalf of the TokenKeeper contract. Only the contract owner or a whitelisted contract can call this function, and only if transfers are not frozen.
     * @param _token The address of the ERC20 token.
     * @param _spender The address to be approved as a spender.
     * @param _approveAmount The amount of tokens the spender is approved to spend.
     */
    function approveToken(
        address _token,
        address _spender,
        uint256 _approveAmount
    ) external override notFrozen onlyOwnerOrWhitelistContract nonReentrant {
        SafeERC20Upgradeable.safeApprove(IERC20Upgradeable(_token), _spender, _approveAmount);
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @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 `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @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);
}

// File: contracts/staking/StakingPoolRewarder.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "../interfaces/IStakingPoolRewarder.sol";
import "../libraries/TransferHelper.sol";

/**
 * @title StakingPoolRewarder
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice An upgradeable contract for releasing rewards based on a vesting schedule.
 * @dev Utilizes OpenZeppelin's upgradeable contracts for basic contract controls and math operations.
 *      It uses a reentrancy block to prevent reentrancy attacks.
 */
contract StakingPoolRewarder is OwnableUpgradeable, IStakingPoolRewarder {
    using SafeMathUpgradeable for uint256;

    /**
     * @notice Emitted when a new vesting schedule is added for a user
     * @dev The event signals the addition of a new vesting schedule, providing the address of the user, amount to be vested, start time, end time, and vesting step
     * @param user The address of the user
     * @param amount The total amount that will be vested
     * @param startTime The start time of the vesting period
     * @param endTime The end time of the vesting period
     * @param step The interval at which vestable amounts are accumulated
     */
    event VestingScheduleAdded(address indexed user, uint256 amount, uint256 startTime, uint256 endTime, uint256 step);

    /**
     * @notice Emitted when the vesting settings are changed
     * @dev The event signals the change in vesting settings, providing the new percentage allocated to vesting, new claim duration, and new claim step
     * @param percentageToVestingSchedule The new percentage of rewards that will be locked in the vesting schedule
     * @param claimDuration The new duration of claims
     * @param claimStep The new interval at which claims can be made
     */
    event VestingSettingChanged(uint8 percentageToVestingSchedule, uint256 claimDuration, uint256 claimStep);

    /**
     * @notice Emitted when tokens are vested by a user from a pool
     * @dev The event signals the vesting of tokens, providing the user address, the pool id, and the amount vested
     * @param user The address of the user
     * @param poolId The id of the pool from which the tokens were vested
     * @param amount The amount of tokens vested
     */
    event TokenVested(address indexed user, uint256 poolId, uint256 amount);

    /**
     * @notice Struct to represent a vesting schedule for a user
     * @dev Defines a vesting schedule with the amount to be vested, start and end times, vesting step, and the last claim time
     * @param amount Total amount to be vested over the complete period
     * @param startTime Unix timestamp in seconds for the period start time
     * @param endTime Unix timestamp in seconds for the period end time
     * @param step Interval in seconds at which vestable amounts are accumulated
     * @param lastClaimTime Unix timestamp in seconds for the last claim time
     */
    struct VestingSchedule {
        uint128 amount;
        uint32 startTime;
        uint32 endTime;
        uint32 step;
        uint32 lastClaimTime;
    }

    /**
     * @notice Mapping of vesting schedules for each user per staking pool
     * @dev Maps each user address to a mapping of pool ids to vesting schedules
     */
    mapping(address => mapping(uint256 => VestingSchedule)) public vestingSchedules;

    /**
     * @notice Mapping of claimable amounts for each user per staking pool
     * @dev Maps each user address to a mapping of pool ids to claimable amounts
     */
    mapping(address => mapping(uint256 => uint256)) public claimableAmounts;

    /**
     * @notice The address of the staking pools
     * @dev The staking pool contract's address
     */
    address public stakingPools;

    /**
     * @notice The token to be used as rewards
     * @dev The contract address of the ERC20 token to be used as rewards
     */
    address public rewardToken;

    /**
     * @notice The dispatcher of the rewards
     * @dev The contract address of the reward dispatcher
     */
    address public rewardDispatcher;

    /**
     * @notice The percentage of the rewards to be locked in the vesting schedule
     * @dev The proportion (out of 100) of rewards that will be vested over time
     */
    uint8 public percentageToVestingSchedule;

    /**
     * @notice The duration of the claims in seconds
     * @dev The total duration of the vesting period
     */
    uint256 public claimDuration;

    /**
     * @notice The interval at which the claims can be made
     * @dev The step (in seconds) at which the user can claim vested tokens
     */
    uint256 public claimStep;

    /**
     * @notice Flag to block reentrancy
     * @dev Used as a guard to prevent reentrancy attacks
     */
    bool private locked;

    /**
     * @notice Modifier to block reentrancy attacks
     * @dev Requires that the contract is not currently executing a state-changing external function call
     */
    modifier blockReentrancy() {
        require(!locked, "Reentrancy is blocked");
        locked = true;
        _;
        locked = false;
    }

    /**
     * @notice Initializes the StakingPoolRewarder contract
     * @dev Initializes the contract with the given parameters. Requires that the addresses are not zero addresses.
     * @param _stakingPools The address of the staking pools
     * @param _rewardToken The token to be used as rewards
     * @param _rewardDispatcher The dispatcher of the rewards
     * @param _percentageToVestingSchedule The percentage of the rewards to be locked in the vesting schedule
     * @param _claimDuration The duration of the claims in seconds
     * @param _claimStep The interval at which the claims can be made
     */
    function __StakingPoolRewarder_init(
        address _stakingPools,
        address _rewardToken,
        address _rewardDispatcher,
        uint8 _percentageToVestingSchedule,
        uint256 _claimDuration,
        uint256 _claimStep
    ) public initializer {
        __Ownable_init();
        require(_stakingPools != address(0), "StakingPoolRewarder: stakingPools zero address");
        require(_rewardToken != address(0), "StakingPoolRewarder: rewardToken zero address");
        require(_rewardDispatcher != address(0), "StakingPoolRewarder: rewardDispatcher zero address");

        stakingPools = _stakingPools;
        rewardToken = _rewardToken;
        rewardDispatcher = _rewardDispatcher;

        percentageToVestingSchedule = _percentageToVestingSchedule;
        claimDuration = _claimDuration;
        claimStep = _claimStep;
    }

    /**
     * @notice Modifier to allow only the staking pools to execute a function
     * @dev Requires that the caller is the staking pools
     */
    modifier onlyStakingPools() {
        require(msg.sender == stakingPools, "StakingPoolRewarder: only StakingPools allowed to call");
        _;
    }

    /**
     * @notice Updates the vesting setting
     * @dev Allows the owner to change the percentage of the rewards that go to the vesting schedule and the duration and interval of the claims. Emits the VestingSettingChanged event.
     * @param _percentageToVestingSchedule The new percentage of the rewards to be locked in the vesting schedule
     * @param _claimDuration The new duration of the claims in seconds
     * @param _claimStep The new interval at which the claims can be made
     */
    function updateVestingSetting(
        uint8 _percentageToVestingSchedule,
        uint256 _claimDuration,
        uint256 _claimStep
    ) external onlyOwner {
        percentageToVestingSchedule = _percentageToVestingSchedule;
        claimDuration = _claimDuration;
        claimStep = _claimStep;

        emit VestingSettingChanged(_percentageToVestingSchedule, _claimDuration, _claimStep);
    }

    /**
     * @notice Sets the reward dispatcher
     * @dev Allows the owner to change the address of the reward dispatcher. Requires that the new address is not a zero address.
     * @param _rewardDispatcher The new address of the reward dispatcher
     */
    function setRewardDispatcher(address _rewardDispatcher) external onlyOwner {
        require(_rewardDispatcher != address(0), "StakingPoolRewarder: rewardDispatcher zero address");
        rewardDispatcher = _rewardDispatcher;
    }

    /**
     * @notice Update the vesting schedule for a user.
     * @dev Updates the vesting schedule for a given user with new parameters. Emits a VestingScheduleAdded event.
     * @param user Address of the user.
     * @param poolId The id of the staking pool.
     * @param amount Total amount to be vested over the period.
     * @param startTime Unix timestamp in seconds for the period start time.
     * @param endTime Unix timestamp in seconds for the period end time.
     * @param step Interval in seconds at which vestable amounts are accumulated.
     */
    function updateVestingSchedule(
        address user,
        uint256 poolId,
        uint256 amount,
        uint256 startTime,
        uint256 endTime,
        uint256 step
    ) private {
        require(user != address(0), "StakingPoolRewarder: zero address");
        require(amount > 0, "StakingPoolRewarder: zero amount");
        require(startTime < endTime, "StakingPoolRewarder: invalid time range");
        require(step > 0 && endTime.sub(startTime) % step == 0, "StakingPoolRewarder: invalid step");

        // Overflow checks
        require(uint256(uint128(amount)) == amount, "StakingPoolRewarder: amount overflow");
        require(uint256(uint32(startTime)) == startTime, "StakingPoolRewarder: startTime overflow");
        require(uint256(uint32(endTime)) == endTime, "StakingPoolRewarder: endTime overflow");
        require(uint256(uint32(step)) == step, "StakingPoolRewarder: step overflow");

        vestingSchedules[user][poolId] = VestingSchedule({
            amount: uint128(amount),
            startTime: uint32(startTime),
            endTime: uint32(endTime),
            step: uint32(step),
            lastClaimTime: uint32(startTime)
        });

        emit VestingScheduleAdded(user, amount, startTime, endTime, step);
    }

    /**
     * @notice Calculates the total reward for a user.
     * @dev It calculates the total amount of reward that a user could claim at this moment. It includes withdrawableFromVesting, unvestedAmount, and claimableAmount. Overrides the function in the parent contract.
     * @param user Address of the user.
     * @param poolId The id of the staking pool.
     * @return total The total reward amount that user could claim at this moment.
     */
    function calculateTotalReward(address user, uint256 poolId) external view override returns (uint256 total) {
        (uint256 withdrawableFromVesting, , ) = _calculateWithdrawableFromVesting(user, poolId, block.timestamp);
        uint256 claimableAmount = claimableAmounts[user][poolId];
        uint256 unvestedAmount = _calculateUnvestedAmountAtCurrentStep(user, poolId, block.timestamp);
        return withdrawableFromVesting.add(unvestedAmount).add(claimableAmount);
    }

    /**
     * @notice Calculates the withdrawable reward for a user.
     * @dev It calculates the total amount of reward that a user could withdraw at this moment. It includes withdrawableFromVesting and claimableAmount. Overrides the function in the parent contract.
     * @param user Address of the user.
     * @param poolId The id of the staking pool.
     * @return total The total reward amount that user could withdraw at this moment.
     */
    function calculateWithdrawableReward(address user, uint256 poolId) external view override returns (uint256 total) {
        (uint256 withdrawableFromVesting, , ) = _calculateWithdrawableFromVesting(user, poolId, block.timestamp);
        uint256 claimableAmount = claimableAmounts[user][poolId];
        return withdrawableFromVesting.add(claimableAmount);
    }

    /**
     * @notice Calculates the amount withdrawable from vesting for a user.
     * @dev Returns the amount that can be withdrawn from the vesting schedule for a given user at the current block timestamp.
     * @param user Address of the user.
     * @param poolId The id of the staking pool.
     * @return amount The amount withdrawable from vesting at this moment.
     */
    function calculateWithdrawableFromVesting(address user, uint256 poolId) external view returns (uint256 amount) {
        (uint256 withdrawable, , ) = _calculateWithdrawableFromVesting(user, poolId, block.timestamp);
        return withdrawable;
    }

    /**
     * @notice Calculates the amount withdrawable from vesting for a user.
     * @dev Calculates the amount that can be withdrawn from the vesting schedule for a given user, the new claim time, and whether all amounts have been vested. If the amount or vesting schedule is zero, or the timestamp is before the start time or the current step time is before or equal to the last claim time, it returns zero. If all amounts have been vested, it returns the total amount to vest minus the amount already vested. If it's partially vested, it returns the amount to vest for the steps to vest.
     * @param user Address of the user.
     * @param poolId The id of the staking pool.
     * @param timestamp Current timestamp.
     * @return amount The amount withdrawable from vesting at this moment.
     * @return newClaimTime The new claim time.
     * @return allVested Whether all amounts have been vested.
     */
    function _calculateWithdrawableFromVesting(
        address user,
        uint256 poolId,
        uint256 timestamp
    )
        private
        view
        returns (
            uint256 amount,
            uint256 newClaimTime,
            bool allVested
        )
    {
        VestingSchedule memory vestingSchedule = vestingSchedules[user][poolId];
        if (vestingSchedule.amount == 0) return (0, 0, false);
        if (timestamp <= uint256(vestingSchedule.startTime)) return (0, 0, false);

        uint256 currentStepTime = MathUpgradeable.min(
            timestamp
            .sub(uint256(vestingSchedule.startTime))
            .div(uint256(vestingSchedule.step))
            .mul(uint256(vestingSchedule.step))
            .add(uint256(vestingSchedule.startTime)),
            uint256(vestingSchedule.endTime)
        );

        if (currentStepTime <= uint256(vestingSchedule.lastClaimTime)) return (0, 0, false);

        uint256 totalSteps = uint256(vestingSchedule.endTime).sub(uint256(vestingSchedule.startTime)).div(
            vestingSchedule.step
        );

        if (currentStepTime == uint256(vestingSchedule.endTime)) {
            // All vested
            uint256 stepsVested = uint256(vestingSchedule.lastClaimTime).sub(uint256(vestingSchedule.startTime)).div(
                vestingSchedule.step
            );
            uint256 amountToVest = uint256(vestingSchedule.amount).sub(
                uint256(vestingSchedule.amount).div(totalSteps).mul(stepsVested)
            );
            return (amountToVest, currentStepTime, true);
        } else {
            // Partially vested
            uint256 stepsToVest = currentStepTime.sub(uint256(vestingSchedule.lastClaimTime)).div(vestingSchedule.step);
            uint256 amountToVest = uint256(vestingSchedule.amount).div(totalSteps).mul(stepsToVest);
            return (amountToVest, currentStepTime, false);
        }
    }

    /**
     * @notice Calculate the amount of tokens that haven't vested at the current step for a specific user and pool.
     * @dev This function uses the timestamp to identify the current step and returns the unvested amount of tokens.
     * @param user The address of the user.
     * @param poolId The id of the pool.
     * @param timestamp The current timestamp.
     * @return The unvested amount.
     */
    function _calculateUnvestedAmountAtCurrentStep(
        address user,
        uint256 poolId,
        uint256 timestamp
    ) private view returns (uint256) {
        if (timestamp < uint256(vestingSchedules[user][poolId].startTime) || vestingSchedules[user][poolId].amount == 0)
            return 0;
        uint256 currentStepTime = MathUpgradeable.min(
            timestamp
            .sub(uint256(vestingSchedules[user][poolId].startTime))
            .div(uint256(vestingSchedules[user][poolId].step))
            .mul(uint256(vestingSchedules[user][poolId].step))
            .add(uint256(vestingSchedules[user][poolId].startTime)),
            uint256(vestingSchedules[user][poolId].endTime)
        );
        return _calculateUnvestedAmount(user, poolId, currentStepTime);
    }

    /**
     * @notice Calculate the amount of tokens that haven't vested at a given step time for a specific user and pool.
     * @dev This function uses the stepTime to identify the current step and returns the unvested amount of tokens.
     * @param user The address of the user.
     * @param poolId The id of the pool.
     * @param stepTime The step time.
     * @return The unvested amount.
     */
    function _calculateUnvestedAmount(
        address user,
        uint256 poolId,
        uint256 stepTime
    ) private view returns (uint256) {
        if (vestingSchedules[user][poolId].amount == 0) return 0;

        uint256 totalSteps = uint256(vestingSchedules[user][poolId].endTime)
        .sub(uint256(vestingSchedules[user][poolId].startTime))
        .div(vestingSchedules[user][poolId].step);
        uint256 stepsVested = stepTime.sub(uint256(vestingSchedules[user][poolId].startTime)).div(
            vestingSchedules[user][poolId].step
        );
        return
            uint256(vestingSchedules[user][poolId].amount).sub(
                uint256(vestingSchedules[user][poolId].amount).div(totalSteps).mul(stepsVested)
            );
    }

    /**
     * @notice Internal function to withdraw vested tokens for a specific user and pool at a given timestamp.
     * @dev This function calculates the amount that can be withdrawn, updates the vesting schedule if necessary, and returns the withdrawn amount.
     * @param user The address of the user.
     * @param poolId The id of the pool.
     * @param timestamp The timestamp at which to perform the withdrawal.
     * @return The amount of tokens withdrawn.
     */
    function _withdrawFromVesting(
        address user,
        uint256 poolId,
        uint256 timestamp
    ) private returns (uint256) {
        (uint256 lastVestedAmount, uint256 newClaimTime, bool allVested) = _calculateWithdrawableFromVesting(
            user,
            poolId,
            timestamp
        );
        if (lastVestedAmount > 0) {
            if (allVested) {
                delete vestingSchedules[user][poolId];
            } else {
                vestingSchedules[user][poolId].lastClaimTime = uint32(newClaimTime);
            }
        }
        return lastVestedAmount;
    }

    /**
     * @notice Handles the reward event for a specific user and pool.
     * @dev This function is called when a user earns rewards from staking in a pool. It simply calls the internal _onReward function.
     * @param poolId The id of the pool.
     * @param user The address of the user.
     * @param amount The amount of tokens rewarded.
     * @param entryTime The timestamp at which the reward was earned.
     */
    function onReward(
        uint256 poolId,
        address user,
        uint256 amount,
        uint256 entryTime
    ) external override onlyStakingPools {
        _onReward(poolId, user, amount, entryTime);
    }

    /**
     * @notice Internal function that handles the reward event for a specific user and pool.
     * @dev This function calculates the vested and unvested amounts of the reward, updates the vesting schedule and claimable amounts, and emits a TokenVested event.
     * @param poolId The id of the pool.
     * @param user The address of the user.
     * @param amount The amount of tokens rewarded.
     * @param entryTime The timestamp at which the reward was earned.
     */
    function _onReward(
        uint256 poolId,
        address user,
        uint256 amount,
        uint256 entryTime
    ) private blockReentrancy {
        require(user != address(0), "StakingPoolRewarder: zero address");

        uint256 lastVestedAmount = _withdrawFromVesting(user, poolId, entryTime);

        uint256 newUnvestedAmount = 0;
        uint256 newVestedAmount = 0;
        if (amount > 0) {
            newUnvestedAmount = amount.mul(uint256(percentageToVestingSchedule)).div(100);
            newVestedAmount = amount.sub(newUnvestedAmount);
        }

        if (newUnvestedAmount > 0) {
            uint256 lastUnvestedAmount = _calculateUnvestedAmountAtCurrentStep(user, poolId, entryTime);
            updateVestingSchedule(
                user,
                poolId,
                newUnvestedAmount.add(lastUnvestedAmount),
                entryTime,
                entryTime.add(claimDuration),
                claimStep
            );
        }

        uint256 newEntryVestedAmount = _withdrawFromVesting(user, poolId, block.timestamp);
        uint256 totalVested = lastVestedAmount.add(newVestedAmount).add(newEntryVestedAmount);
        claimableAmounts[user][poolId] = claimableAmounts[user][poolId].add(totalVested);
        emit TokenVested(user, poolId, totalVested);
    }

    /**
     * @notice Allows a user to claim vested rewards from a specific pool.
     * @dev This function checks if there are claimable rewards, transfers the rewards to the user, and returns the claimed amount.
     * @param poolId The id of the pool.
     * @param user The address of the user.
     * @return The amount of tokens claimed.
     */
    function claimVestedReward(uint256 poolId, address user)
        external
        override
        onlyStakingPools
        blockReentrancy
        returns (uint256)
    {
        require(poolId > 0, "StakingPoolRewarder: poolId is 0");
        uint256 claimableAmount = claimableAmounts[user][poolId];
        claimableAmounts[user][poolId] = 0;
        if (claimableAmount > 0) {
            TransferHelper.safeTransferFrom(rewardToken, rewardDispatcher, user, claimableAmount);
        }

        return claimableAmount;
    }
}

// File: contracts/libraries/TransferHelper.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.4;

/**
 * @title TransferHelper library
 * @author DeOrderBook
 * @custom:license Adapted from Uniswap's V3 TransferHelper.sol
 *
 *                Copyright (c) 2023 DeOrderBook
 *
 *           This program is free software; you can redistribute it and/or
 *           modify it under the terms of the GNU General Public License
 *           as published by the Free Software Foundation; either version 2
 *           of the License, or (at your option) any later version.
 *
 *           This program is distributed in the hope that it will be useful,
 *           but WITHOUT ANY WARRANTY; without even the implied warranty of
 *           MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *           GNU General Public License for more details.
 *
 *           You should have received a copy of the GNU General Public License
 *           along with this program; if not, write to the Free Software
 *           Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @notice Helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
 * @dev This library provides safe wrappers around ERC20 token operations and ETH transfers, checking for success and throwing appropriate errors if needed.
 */
library TransferHelper {
    /**
     * @notice Approve spending of an ERC20 token by another address
     * @dev Approves spending of an ERC20 token by another address, checking the return value and handling potential errors
     * @param token The address of the ERC20 token
     * @param to The address to approve spending for
     * @param value The amount to approve spending for
     */
    function safeApprove(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: APPROVE_FAILED");
    }

    /**
     * @notice Transfer ERC20 tokens to another address
     * @dev Transfers ERC20 tokens to another address, checking the return value and handling potential errors
     * @param token The address of the ERC20 token
     * @param to The address to transfer tokens to
     * @param value The amount of tokens to transfer
     */
    function safeTransfer(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FAILED");
    }

    /**
     * @notice Transfer ERC20 tokens from one address to another
     * @dev Transfers ERC20 tokens from one address to another, checking the return value and handling potential errors
     * @param token The address of the ERC20 token
     * @param from The address to transfer tokens from
     * @param to The address to transfer tokens to
     * @param value The amount of tokens to transfer
     */
    function safeTransferFrom(address token, address from, address to, uint value) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FROM_FAILED");
    }

    /**
     * @notice Transfer ETH to another address
     * @dev Transfers ETH to another address, checking for success and handling potential errors, such as out-of-gas or revert
     * @param to The address to transfer ETH to
     * @param value The amount of ETH to transfer
     */
    function safeTransferETH(address to, uint value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, "TransferHelper: ETH_TRANSFER_FAILED");
    }
}

// File: contracts/staking/StakingPools.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol";
import "../interfaces/IStakingPoolRewarder.sol";
import "../interfaces/IStakingPools.sol";

/**
 * @title StakingPools
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice A contract for staking tokens in pools in exchange for rewards.
 * @dev The contract provides functionality for users to stake tokens in specific pools, earn rewards and claim them. The rewards are managed by a separate rewarder contract.
 *      The contract uses OpenZeppelin's Ownable for managing ownership and upgradeability to allow for future improvements without disrupting the contract's main operations.
 */
contract StakingPools is OwnableUpgradeable, IStakingPools {
    using SafeMathUpgradeable for uint256;

    /**
     * @notice Emitted when a new staking pool is created
     * @dev Logs the newly created pool's ID, the staked token, option contract, start block, end block, and reward per block
     * @param poolId The ID of the newly created pool
     * @param token The address of the token to be staked
     * @param optionContract The address of the option contract associated with the pool
     * @param startBlock The block number from which staking begins
     * @param endBlock The block number at which staking ends
     * @param rewardPerBlock The reward to be distributed per block for the pool
     */
    event PoolCreated(
        uint256 indexed poolId,
        address indexed token,
        address indexed optionContract,
        uint256 startBlock,
        uint256 endBlock,
        uint256 rewardPerBlock
    );

    /**
     * @notice Emitted when a staking pool's end block is extended
     * @dev Logs the pool's ID, its old end block and the new extended end block
     * @param poolId The ID of the pool
     * @param oldEndBlock The old end block of the pool
     * @param newEndBlock The new end block after extension
     */
    event PoolEndBlockExtended(uint256 indexed poolId, uint256 oldEndBlock, uint256 newEndBlock);

    /**
     * @notice Emitted when a staking pool's reward rate is changed
     * @dev Logs the pool's ID, its old reward per block and the new reward per block
     * @param poolId The ID of the pool
     * @param oldRewardPerBlock The old reward per block of the pool
     * @param newRewardPerBlock The new reward per block of the pool
     */
    event PoolRewardRateChanged(uint256 indexed poolId, uint256 oldRewardPerBlock, uint256 newRewardPerBlock);

    /**
     * @notice Emitted when the rewarder contract address is changed
     * @dev Logs the old rewarder address and the new rewarder address
     * @param oldRewarder The old rewarder contract address
     * @param newRewarder The new rewarder contract address
     */
    event RewarderChanged(address oldRewarder, address newRewarder);

    /**
     * @notice Emitted when tokens are staked in a pool
     * @dev Logs the pool's ID, the staker's address, the staked token's address, and the staked amount
     * @param poolId The ID of the pool
     * @param staker The address of the staker
     * @param token The address of the staked token
     * @param amount The amount of tokens staked
     */
    event Staked(uint256 indexed poolId, address indexed staker, address token, uint256 amount);

    /**
     * @notice Emitted when tokens are unstaked from a pool
     * @dev Logs the pool's ID, the staker's address, the staked token's address, and the unstaked amount
     * @param poolId The ID of the pool
     * @param staker The address of the staker
     * @param token The address of the staked token
     * @param amount The amount of tokens unstaked
     */
    event Unstaked(uint256 indexed poolId, address indexed staker, address token, uint256 amount);

    /**
     * @notice Emitted when rewards are redeemed from a pool
     * @dev Logs the pool's ID, the staker's address, the rewarder's address, and the redeemed reward amount
     * @param poolId The ID of the pool
     * @param staker The address of the staker
     * @param rewarder The address of the rewarder
     * @param amount The amount of rewards redeemed
     */
    event RewardRedeemed(uint256 indexed poolId, address indexed staker, address rewarder, uint256 amount);

    /**
     * @notice Emitted when the active state of a pool is changed
     * @dev Logs the pool's ID and its new active state
     * @param poolId The ID of the pool
     * @param isActive The new active state of the pool
     */
    event IsActiveChanged(uint256 indexed poolId, bool isActive);

    /**
     * @notice Emitted when the factory address is changed
     * @dev Logs the old factory address and the new factory address
     * @param oldFactory The old factory address
     * @param newFactory The new factory address
     */
    event FactoryChanged(address oldFactory, address newFactory);

    /**
     * @notice Represents a staking pool
     * @dev Contains pool details including start and end block for reward accumulation, reward per block, and the staking token's address
     */
    struct PoolInfo {
        uint256 startBlock; // the block from which rewards accumulation starts
        uint256 endBlock; // the block at which rewards accumulation ends
        uint256 rewardPerBlock; // the total rewards given to the pool per block
        address poolToken; // the address of the SNIPER token being staked in the pool
        address optionContract; // the address of the Option the SNIPER being staked in the pool belongs to
        bool isActive; // whether the pool is or is not open for staking
    }

    /**
     * @notice Represents a pool's staking data
     * @dev Contains information about total staked amount, accumulated reward per share, and the last block at which the accumulated reward was updated
     */
    struct PoolData {
        uint256 totalStakeAmount; // the total amount of tokens staked in the pool
        uint256 accuRewardPerShare; // the total amount of rewards divided with precision by the total number of tokens staked
        uint256 accuRewardLastUpdateBlock; // the block number at which accuRewardPerShare was last updated
    }

    /**
     * @notice Represents a user's staking data in a pool
     * @dev Contains information about the amount of tokens staked by the user, pending rewards, and the accumulated reward per share at the user's last staking/unstaking action
     */
    struct UserData {
        uint256 stakeAmount; // amount of tokens the user has in stake
        uint256 pendingReward; // amount of reward that can be redeemed by the user up to his latest action
        uint256 entryAccuRewardPerShare; // the accuRewardPerShare value at the user's last stake/unstake action
        uint256 entryTime; // the timestamp of the block the user entered the pool
    }

    /**
     * @notice The factory contract address
     * @dev Stores the address of the factory contract responsible for creating option contracts
     */
    address public optionFactory;

    /**
     * @notice The ID of the last created pool
     * @dev Stores the ID of the last created pool. The first pool has an ID of 1.
     */
    uint256 public lastPoolId;

    /**
     * @notice The rewarder contract
     * @dev An instance of IStakingPoolRewarder which handles reward logic
     */
    IStakingPoolRewarder public rewarder;

    /**
     * @notice Maps a pool ID to its PoolInfo struct
     * @dev Contains details for each staking pool
     */
    mapping(uint256 => PoolInfo) public poolInfos;

    /**
     * @notice Maps a pool ID to its PoolData struct
     * @dev Contains staking data for each pool
     */
    mapping(uint256 => PoolData) public poolData;

    /**
     * @notice Maps a pool ID to a mapping of an address to its UserData struct
     * @dev Contains user staking data for each pool
     */
    mapping(uint256 => mapping(address => UserData)) public userData;

    /**
     * @notice A constant used for precision loss prevention
     * @dev A large constant to keep precision intact when dealing with very small amounts
     */
    uint256 private constant ACCU_REWARD_MULTIPLIER = 10**20;

    /**
     * @notice Constant for the `transfer` function selector
     * @dev Bytes4 constant for the `transfer(address,uint256)` function selector to be used in calling ERC20 contracts
     */
    bytes4 private constant TRANSFER_SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)")));

    /**
     * @notice Constant for the `approve` function selector
     * @dev Bytes4 constant for the `approve(address,uint256)` function selector to be used in calling ERC20 contracts
     */
    bytes4 private constant APPROVE_SELECTOR = bytes4(keccak256(bytes("approve(address,uint256)")));

    /**
     * @notice Constant for the `transferFrom` function selector
     * @dev Bytes4 constant for the `transferFrom(address,address,uint256)` function selector to be used in calling ERC20 contracts
     */
    bytes4 private constant TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)")));

    /**
     * @notice Ensures the pool exists
     * @dev Checks if the pool with the specified ID exists
     */
    modifier onlyPoolExists(uint256 poolId) {
        require(poolInfos[poolId].endBlock > 0, "StakingPools: pool not found");
        _;
    }

    /**
     * @notice Ensures the pool is active
     * @dev Checks if the current block number is within the start and end block of the pool and if the pool is active
     */
    modifier onlyPoolActive(uint256 poolId) {
        require(
            block.number >= poolInfos[poolId].startBlock &&
                block.number < poolInfos[poolId].endBlock &&
                poolInfos[poolId].isActive,
            "StakingPools: pool not active"
        );
        _;
    }

    /**
     * @notice Ensures the pool has not ended
     * @dev Checks if the current block number is less than the end block of the pool
     */
    modifier onlyPoolNotEnded(uint256 poolId) {
        require(block.number < poolInfos[poolId].endBlock, "StakingPools: pool ended");
        _;
    }

    /**
     * @notice Ensures only the option contract can call the function
     * @dev Checks if the caller is the option contract associated with the pool
     */
    modifier onlyOptionContract(uint256 poolId) {
        require(msg.sender == poolInfos[poolId].optionContract, "StakingPools: only option contract");
        _;
    }

    /**
     * @notice Ensures only the owner or the factory contract can call the function
     * @dev Checks if the caller is the owner of the contract or the factory contract
     */
    modifier onlyOwnerOrFactory() {
        require(
            msg.sender == optionFactory || msg.sender == owner(),
            "StakingPools: caller is not the optionFactory or owner"
        );
        _;
    }

    /**
     * @notice Fetches detailed information for a specific pool
     * @dev This function simply returns the PoolInfo struct from the poolInfos mapping
     * @param poolId The ID of the pool for which to fetch information
     * @return PoolInfo struct containing detailed information about the pool
     */
    function getPoolInfo(uint256 poolId) external view returns (PoolInfo memory) {
        return poolInfos[poolId];
    }

    /**
     * @notice Calculates the pending reward for a specific user in a specific pool
     * @dev This function calculates the pending reward for a user by using the accuRewardPerShare,
     *      the amount the user has staked and the user's pendingReward
     * @param poolId The ID of the pool
     * @param staker The address of the user
     * @return The calculated pending reward for the user
     */
    function getPendingReward(uint256 poolId, address staker) external view returns (uint256) {
        UserData memory currentUserData = userData[poolId][staker];
        PoolInfo memory currentPoolInfo = poolInfos[poolId];
        PoolData memory currentPoolData = poolData[poolId];

        uint256 latestAccuRewardPerShare = currentPoolData.totalStakeAmount > 0
            ? currentPoolData.accuRewardPerShare.add(
                MathUpgradeable
                .min(block.number, currentPoolInfo.endBlock)
                .sub(currentPoolData.accuRewardLastUpdateBlock)
                .mul(currentPoolInfo.rewardPerBlock)
                .mul(ACCU_REWARD_MULTIPLIER)
                .div(currentPoolData.totalStakeAmount)
            )
            : currentPoolData.accuRewardPerShare;

        return
            currentUserData.pendingReward.add(
                currentUserData.stakeAmount.mul(latestAccuRewardPerShare.sub(currentUserData.entryAccuRewardPerShare)).div(
                    ACCU_REWARD_MULTIPLIER
                )
            );
    }

    /**
     * @notice Fetches the amount staked by a specific user in a specific pool
     * @dev This function returns the stakeAmount from the UserData struct
     *      in the userData mapping for the user in the specified pool
     * @param user The address of the user
     * @param poolId The ID of the pool
     * @return The amount staked by the user in the pool
     */
    function getStakingAmountByPoolID(address user, uint256 poolId) external view override returns (uint256) {
        return userData[poolId][user].stakeAmount;
    }

    /**
     * @notice Initializer function for StakingPools contract
     * @dev Calls the initializer of the parent Ownable contract. Only callable once.
     */
    function __StakingPools_init() public initializer {
        __Ownable_init();
    }

    /**
     * @notice Creates a new staking pool
     * @dev The function will create a new pool with the provided parameters and emit a PoolCreated event
     * @param token The token that can be staked in the pool
     * @param optionContract The address of the option contract associated with the pool
     * @param startBlock The block at which staking starts
     * @param endBlock The block at which staking ends
     * @param rewardPerBlock The amount of reward token distributed per block
     */
    function createPool(
        address token,
        address optionContract,
        uint256 startBlock,
        uint256 endBlock,
        uint256 rewardPerBlock
    ) external override onlyOwnerOrFactory {
        require(token != address(0), "StakingPools: zero address");
        require(optionContract != address(0), "StakingPools: zero address");
        require(startBlock > block.number && endBlock > startBlock, "StakingPools: invalid block range");
        require(rewardPerBlock > 0, "StakingPools: reward must be positive");

        uint256 newPoolId = ++lastPoolId;

        poolInfos[newPoolId] = PoolInfo({
            startBlock: startBlock,
            endBlock: endBlock,
            rewardPerBlock: rewardPerBlock,
            poolToken: token,
            optionContract: optionContract,
            isActive: true
        });
        poolData[newPoolId] = PoolData({totalStakeAmount: 0, accuRewardPerShare: 0, accuRewardLastUpdateBlock: startBlock});

        emit PoolCreated(newPoolId, token, optionContract, startBlock, endBlock, rewardPerBlock);
    }

    /**
     * @notice Extends the end block of a pool
     * @dev The function will update the endBlock of a pool and emit a PoolEndBlockExtended event
     * @param poolId The ID of the pool
     * @param newEndBlock The new end block for the pool
     */
    function extendEndBlock(uint256 poolId, uint256 newEndBlock)
        external
        override
        onlyOwnerOrFactory
        onlyPoolExists(poolId)
        onlyPoolNotEnded(poolId)
    {
        uint256 currentEndBlock = poolInfos[poolId].endBlock;
        require(newEndBlock > currentEndBlock, "StakingPools: end block not extended");

        poolInfos[poolId].endBlock = newEndBlock;

        emit PoolEndBlockExtended(poolId, currentEndBlock, newEndBlock);
    }

    /**
     * @notice Sets the per block reward for a pool
     * @dev The function will update the rewardPerBlock of a pool and emit a PoolRewardRateChanged event
     * @param poolId The ID of the pool
     * @param newRewardPerBlock The new reward per block for the pool
     */
    function setPoolReward(uint256 poolId, uint256 newRewardPerBlock)
        external
        onlyOwner
        onlyPoolExists(poolId)
        onlyPoolNotEnded(poolId)
    {
        if (block.number >= poolInfos[poolId].startBlock) {
            // "Settle" rewards up to this block
            _updatePoolAccuReward(poolId);
        }

        // We're deliberately allowing setting the reward rate to 0 here. If it turns
        // out this, or even changing rates at all, is undesirable after deployment, the
        // ownership of this contract can be transferred to a contract incapable of making
        // calls to this function.
        uint256 currentRewardPerBlock = poolInfos[poolId].rewardPerBlock;
        poolInfos[poolId].rewardPerBlock = newRewardPerBlock;

        emit PoolRewardRateChanged(poolId, currentRewardPerBlock, newRewardPerBlock);
    }

    /**
     * @notice Sets the active status of a pool
     * @dev The function will update the isActive field of a pool and emit an IsActiveChanged event
     * @param poolId The ID of the pool
     * @param isActive The new active status for the pool
     */
    function setIsActive(uint256 poolId, bool isActive) external onlyOwner onlyPoolExists(poolId) {
        poolInfos[poolId].isActive = isActive;

        emit IsActiveChanged(poolId, isActive);
    }

    /**
     * @notice Sets the rewarder contract
     * @dev The function will update the rewarder field of the contract and emit a RewarderChanged event
     * @param newRewarder The address of the new rewarder contract
     */
    function setRewarder(address newRewarder) external onlyOwner {
        require(newRewarder != address(0), "StakingPools: zero address");

        address oldRewarder = address(rewarder);
        rewarder = IStakingPoolRewarder(newRewarder);

        emit RewarderChanged(oldRewarder, newRewarder);
    }

    /**
     * @notice Sets the factory contract
     * @dev The function will update the optionFactory field of the contract and emit a FactoryChanged event
     * @param newFactory The address of the new factory contract
     */
    function setFactory(address newFactory) external onlyOwner {
        require(newFactory != address(0), "StakingPools: zero address");

        address oldFactory = optionFactory;
        optionFactory = newFactory;

        emit FactoryChanged(oldFactory, optionFactory);
    }

    /**
     * @notice Allows a user to stake a certain amount in a specific pool
     * @dev Updates the pool's accumulated rewards and the user's reward, then stakes the specified amount.
     * @param poolId The ID of the pool
     * @param amount The amount to stake
     */
    function stake(uint256 poolId, uint256 amount) external onlyPoolExists(poolId) onlyPoolActive(poolId) {
        _updatePoolAccuReward(poolId);
        _updateStakerReward(poolId, msg.sender);

        _stake(poolId, msg.sender, amount);
    }

    /**
     * @notice Allows a user to unstake a certain amount from a specific pool
     * @dev Updates the pool's accumulated rewards and the user's reward, then unstakes the specified amount.
     * @param poolId The ID of the pool
     * @param amount The amount to unstake
     */
    function unstake(uint256 poolId, uint256 amount) external onlyPoolExists(poolId) {
        _updatePoolAccuReward(poolId);
        _updateStakerReward(poolId, msg.sender);

        _unstake(poolId, msg.sender, amount);
    }

    /**
     * @notice Allows an option contract to stake on behalf of a user in a specific pool
     * @dev Updates the pool's accumulated rewards and the user's reward, then stakes the specified amount. Additionally, updates the user's entry time and vests any pending rewards.
     * @param poolId The ID of the pool
     * @param amount The amount to stake
     * @param user The address of the user
     */
    function stakeFor(
        uint256 poolId,
        uint256 amount,
        address user
    ) external override onlyPoolExists(poolId) onlyPoolActive(poolId) onlyOptionContract(poolId) {
        require(user != address(0), "StakingPools: zero address");

        _updatePoolAccuReward(poolId);
        _updateStakerReward(poolId, user);

        require(amount > 0, "StakingPools: cannot stake zero amount");

        userData[poolId][user].stakeAmount = userData[poolId][user].stakeAmount.add(amount);
        poolData[poolId].totalStakeAmount = poolData[poolId].totalStakeAmount.add(amount);

        _vestPendingRewards(poolId, user);
        userData[poolId][user].entryTime = block.timestamp;

        emit Staked(poolId, user, poolInfos[poolId].poolToken, amount);
    }

    /**
     * @notice Allows an option contract to unstake on behalf of a user from a specific pool
     * @dev Updates the pool's accumulated rewards and the user's reward, then unstakes the specified amount. Transfers the unstaked tokens back to the user.
     * @param poolId The ID of the pool
     * @param amount The amount to unstake
     * @param user The address of the user
     */
    function unstakeFor(
        uint256 poolId,
        uint256 amount,
        address user
    ) external override onlyPoolExists(poolId) onlyOptionContract(poolId) {
        require(user != address(0), "StakingPools: zero address");

        _updatePoolAccuReward(poolId);
        _updateStakerReward(poolId, user);

        require(amount > 0, "StakingPools: cannot unstake zero amount");

        userData[poolId][user].stakeAmount = userData[poolId][user].stakeAmount.sub(amount);
        poolData[poolId].totalStakeAmount = poolData[poolId].totalStakeAmount.sub(amount);

        safeTransfer(poolInfos[poolId].poolToken, user, amount);

        emit Unstaked(poolId, user, poolInfos[poolId].poolToken, amount);
    }

    /**
     * @notice Allows a user to emergency unstake all staked tokens from a specific pool
     * @dev Unstakes all staked tokens and forfeits any pending rewards to prevent abuse. This function can be used when the user wants to quickly withdraw all staked tokens without claiming the rewards.
     * @param poolId The ID of the pool
     */
    function emergencyUnstake(uint256 poolId) external onlyPoolExists(poolId) {
        _unstake(poolId, msg.sender, userData[poolId][msg.sender].stakeAmount);

        userData[poolId][msg.sender].pendingReward = 0;
    }

    /**
     * @notice Allows a user to redeem rewards from a specific pool
     * @dev Calls the _redeemRewardsByAddress private function with the caller's address to claim rewards from the pool.
     * @param poolId The ID of the pool
     */
    function redeemRewards(uint256 poolId) external {
        _redeemRewardsByAddress(poolId, msg.sender);
    }

    /**
     * @notice Allows a user to redeem rewards from a list of pools
     * @dev Calls the _redeemRewardsByAddress private function for each pool in the list to claim rewards from the pools.
     * @param poolIds An array of the pool IDs
     */
    function redeemRewardsByList(uint256[] memory poolIds) external {
        for (uint256 i = 0; i < poolIds.length; i++) {
            _redeemRewardsByAddress(poolIds[i], msg.sender);
        }
    }

    /**
     * @notice Allows to redeem rewards from a specific pool for a specific user
     * @dev Calls the _redeemRewardsByAddress private function with the specified user address to claim rewards from the pool.
     * @param poolId The ID of the pool
     * @param user The address of the user
     */
    function redeemRewardsByAddress(uint256 poolId, address user) external override {
        _redeemRewardsByAddress(poolId, user);
    }

    /**
     * @notice Allows a user to unstake a certain amount from a specific pool and redeem the rewards
     * @dev Updates the pool's accumulated rewards and the user's reward, then unstakes the specified amount. Afterwards, redeems the rewards for the user.
     * @param poolId The ID of the pool
     * @param amount The amount to unstake
     */
    function unstakeAndRedeemReward(uint256 poolId, uint256 amount) external {
        _updatePoolAccuReward(poolId);
        _updateStakerReward(poolId, msg.sender);

        _unstake(poolId, msg.sender, amount);

        _redeemRewardsByAddress(poolId, msg.sender);
    }

    /**
     * @notice Redeems the rewards for a user in a specific pool
     * @dev Updates the pool's and the user's rewards, checks that the rewarder is set, vests any pending rewards and claims vested rewards for the user.
     * @param poolId The ID of the pool
     * @param user The address of the user
     */
    function _redeemRewardsByAddress(uint256 poolId, address user) private onlyPoolExists(poolId) {
        require(user != address(0), "StakingPools: zero address");

        _updatePoolAccuReward(poolId);
        _updateStakerReward(poolId, user);

        require(address(rewarder) != address(0), "StakingPools: rewarder not set");

        _vestPendingRewards(poolId, user);

        uint256 claimed = rewarder.claimVestedReward(poolId, user);

        emit RewardRedeemed(poolId, user, address(rewarder), claimed);
    }

    /**
     * @notice Vests the pending rewards for a user in a specific pool
     * @dev Resets the user's pending reward to zero and transfers them to the rewarder contract, specifying the user's entry time.
     * @param poolId The ID of the pool
     * @param user The address of the user
     */
    function _vestPendingRewards(uint256 poolId, address user) private onlyPoolExists(poolId) {
        uint256 rewardToVest = userData[poolId][user].pendingReward;
        userData[poolId][user].pendingReward = 0;
        rewarder.onReward(poolId, user, rewardToVest, userData[poolId][user].entryTime);
    }

    /**
     * @notice Allows a user to stake a certain amount in a specific pool
     * @dev Transfers the specified amount of the pool's token from the user to the contract, updates the user's stake amount, and resets the user's entry time.
     * @param poolId The ID of the pool
     * @param user The address of the user
     * @param amount The amount to stake
     */
    function _stake(
        uint256 poolId,
        address user,
        uint256 amount
    ) private {
        require(amount > 0, "StakingPools: cannot stake zero amount");

        userData[poolId][user].stakeAmount = userData[poolId][user].stakeAmount.add(amount);
        poolData[poolId].totalStakeAmount = poolData[poolId].totalStakeAmount.add(amount);

        safeTransferFrom(poolInfos[poolId].poolToken, user, address(this), amount);

        _vestPendingRewards(poolId, user);
        userData[poolId][user].entryTime = block.timestamp;

        emit Staked(poolId, user, poolInfos[poolId].poolToken, amount);
    }

    /**
     * @notice Allows a user to unstake a certain amount from a specific pool
     * @dev Transfers the specified amount of the pool's token back to the user and updates the user's stake amount.
     * @param poolId The ID of the pool
     * @param user The address of the user
     * @param amount The amount to unstake
     */
    function _unstake(
        uint256 poolId,
        address user,
        uint256 amount
    ) private {
        require(amount > 0, "StakingPools: cannot unstake zero amount");

        userData[poolId][user].stakeAmount = userData[poolId][user].stakeAmount.sub(amount);
        poolData[poolId].totalStakeAmount = poolData[poolId].totalStakeAmount.sub(amount);

        safeTransfer(poolInfos[poolId].poolToken, user, amount);

        emit Unstaked(poolId, user, poolInfos[poolId].poolToken, amount);
    }

    /**
     * @notice Updates the accumulated reward per share for a specific pool
     * @dev If there has been more than one block since the last update and the total staked amount is positive, the accumulated reward per share is increased based on the number of blocks since the last update and the pool's reward per block.
     * @param poolId The ID of the pool
     */
    function _updatePoolAccuReward(uint256 poolId) private {
        PoolInfo storage currentPoolInfo = poolInfos[poolId];
        PoolData storage currentPoolData = poolData[poolId];

        uint256 appliedUpdateBlock = MathUpgradeable.min(block.number, currentPoolInfo.endBlock);
        uint256 durationInBlocks = appliedUpdateBlock.sub(currentPoolData.accuRewardLastUpdateBlock);

        if (durationInBlocks > 0) {
            if (currentPoolData.totalStakeAmount > 0) {
                currentPoolData.accuRewardPerShare = currentPoolData.accuRewardPerShare.add(
                    durationInBlocks.mul(currentPoolInfo.rewardPerBlock).mul(ACCU_REWARD_MULTIPLIER).div(
                        currentPoolData.totalStakeAmount
                    )
                );
            }
            currentPoolData.accuRewardLastUpdateBlock = appliedUpdateBlock;
        }
    }

    /**
     * @notice Updates the pending reward and entry accumulated reward per share for a staker in a specific pool
     * @dev If the pool's accumulated reward per share has increased since the staker's entry, the staker's pending reward is increased based on their stake amount and the difference in accumulated reward per share, and their entry accumulated reward per share is updated.
     * @param poolId The ID of the pool
     * @param staker The address of the staker
     */
    function _updateStakerReward(uint256 poolId, address staker) private {
        UserData storage currentUserData = userData[poolId][staker];
        PoolData storage currentPoolData = poolData[poolId];

        uint256 stakeAmount = currentUserData.stakeAmount;
        uint256 stakerEntryRate = currentUserData.entryAccuRewardPerShare;
        uint256 accuDifference = currentPoolData.accuRewardPerShare.sub(stakerEntryRate);

        if (accuDifference > 0) {
            currentUserData.pendingReward = currentUserData.pendingReward.add(
                stakeAmount.mul(accuDifference).div(ACCU_REWARD_MULTIPLIER)
            );
            currentUserData.entryAccuRewardPerShare = currentPoolData.accuRewardPerShare;
        }
    }

    /**
     * @notice Allows the contract to approve the transfer of a certain amount of a token on behalf of the contract
     * @dev Calls the approve function of the token contract with the specified spender and amount, and requires that the call was successful.
     * @param token The address of the token contract
     * @param spender The address of the spender
     * @param amount The amount to approve
     */
    function safeApprove(
        address token,
        address spender,
        uint256 amount
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(APPROVE_SELECTOR, spender, amount));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "StakingPools: approve failed");
    }

    /**
     * @notice Allows the contract to transfer a certain amount of a token to a recipient
     * @dev Calls the transfer function of the token contract with the specified recipient and amount, and requires that the call was successful.
     * @param token The address of the token contract
     * @param recipient The address of the recipient
     * @param amount The amount to transfer
     */
    function safeTransfer(
        address token,
        address recipient,
        uint256 amount
    ) private {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(TRANSFER_SELECTOR, recipient, amount));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "StakingPools: transfer failed");
    }

    /**
     * @notice Allows the contract to transfer a certain amount of a token from a sender to a recipient
     * @dev Calls the transferFrom function of the token contract with the specified sender, recipient, and amount, and requires that the call was successful.
     * @param token The address of the token contract
     * @param sender The address of the sender
     * @param recipient The address of the recipient
     * @param amount The amount to transfer
     */
    function safeTransferFrom(
        address token,
        address sender,
        address recipient,
        uint256 amount
    ) private {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(TRANSFERFROM_SELECTOR, sender, recipient, amount)
        );
        require(success && (data.length == 0 || abi.decode(data, (bool))), "StakingPools: transferFrom failed");
    }
}

// File: contracts/interfaces/IStakingPools.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title IStakingPools interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for staking pools contract
 */
interface IStakingPools {
    /**
     * @notice Create a new staking pool
     * @dev Creates a new staking pool with the specified parameters
     * @param token The address of the ERC20 token contract to be staked
     * @param optionContract The address of the Option contract
     * @param startBlock The block number at which staking begins
     * @param endBlock The block number at which staking ends
     * @param rewardPerBlock The amount of reward tokens to be distributed per block
     */
    function createPool(
        address token,
        address optionContract,
        uint256 startBlock,
        uint256 endBlock,
        uint256 rewardPerBlock
    ) external;

    /**
     * @notice Extend the end block of a staking pool
     * @dev Extends the end block of a staking pool with the specified pool ID
     * @param poolId The ID of the staking pool to extend
     * @param newEndBlock The new end block of the staking pool
     */
    function extendEndBlock(uint256 poolId, uint256 newEndBlock) external;

    /**
     * @notice Get the staking amount for a user and pool ID
     * @dev Gets the staking amount for the specified user and pool ID
     * @param user The address of the user to get the staking amount for
     * @param poolId The ID of the staking pool to get the staking amount for
     * @return The staking amount for the specified user and pool ID
     */
    function getStakingAmountByPoolID(address user, uint256 poolId) external returns (uint256);

    /**
     * @notice Stake tokens on behalf of a user
     * @dev Stakes tokens on behalf of the specified user for the specified staking pool
     * @param poolId The ID of the staking pool to stake tokens for
     * @param amount The amount of tokens to stake
     * @param user The address of the user to stake tokens for
     */
    function stakeFor(uint256 poolId, uint256 amount, address user) external;

    /**
     * @notice Unstake tokens on behalf of a user
     * @dev Unstakes tokens on behalf of the specified user for the specified staking pool
     * @param poolId The ID of the staking pool to unstake tokens for
     * @param amount The amount of tokens to unstake
     * @param user The address of the user to unstake tokens for
     */
    function unstakeFor(uint256 poolId, uint256 amount, address user) external;

    /**
     * @notice Redeem rewards for a user and pool ID
     * @dev Redeems rewards for the specified user and pool ID
     * @param poolId The ID of the staking pool to redeem rewards from
     * @param user The address of the user to redeem rewards for
     */
    function redeemRewardsByAddress(uint256 poolId, address user) external;
}

// File: contracts/option/OptionFactory.sol



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

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol";
import "../interfaces/IOptionFactory.sol";
import "../interfaces/IStakingPools.sol";
import "../interfaces/IDOBStakingPool.sol";
import "../interfaces/IContractGenerator.sol";
import "../interfaces/IOption.sol";

/**
 * @title OptionFactory
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice The OptionFactory contract is the primary control center for creating and managing options and staking pools within the ecosystem. It provides functionalities to set and update various fee ratios, to extend the staking pool end block, to update option funds, and to manage the lifecycle of options.
 * @dev This contract is designated as 'Ownable', meaning it has an owner address, and guards against unauthorized access by checking the 'onlyOwner' modifier. It interfaces with the IOptionFactory, IDOBStakingPool, and IStakingPools contracts to perform various tasks related to option and staking pool management.
 */
contract OptionFactory is OwnableUpgradeable, IOptionFactory {
    using SafeMathUpgradeable for uint256;

    /**
     * @notice The time taken (in seconds) for a block to be mined.
     * @dev This value is public.
     */
    uint8 public blockTime;

    /**
     * @notice The staking reward given per block.
     * @dev This value is public and can be updated by the contract owner.
     */
    uint256 public stakingRewardPerBlock;

    /**
     * @notice The address of the staking pools contract.
     * @dev This value is public and can be updated by the contract owner.
     */
    address public stakingPools;

    /**
     * @notice The address of the DOB Staking Pool contract.
     * @dev This value is public and can be updated by the contract owner.
     */
    address public DOBStakingPool;

    /**
     * @notice The address of the bHODL token contract.
     * @dev This value is public.
     */
    address public bHODL;

    /**
     * @notice The address of the uHODL token contract.
     * @dev This value is public.
     */
    address public uHODL;

    /**
     * @notice The address of the distributions contract.
     * @dev This value is public and can be updated by the contract owner.
     */
    address public override distributions;

    /**
     * @notice The address of the ContractGenerator contract.
     * @dev This value is public and can be updated by the contract owner.
     */
    address public ContractGenerator;

    /**
     * @notice The address of the fund contract.
     * @dev This value is public and can be updated by the contract owner.
     */
    address public fund;

    /**
     * @notice The ID of the last option that was created.
     * @dev This value is private and incremented every time a new option is created.
     */
    uint256 private lastOptionId;

    /**
     * @notice The address of the clone bullet contract.
     * @dev This value is private and updated every time a new bullet is cloned.
     */
    address private cloneBullet;

    /**
     * @notice The address of the clone sniper contract.
     * @dev This value is private and updated every time a new sniper is cloned.
     */
    address private cloneSniper;

    /**
     * @notice The ID of the clone option contract.
     * @dev This value is private and updated every time a new option is cloned.
     */
    uint256 private cloneOptionId;

    /**
     * @notice A structure for storing the addresses of the option, sniper, and bullet contracts.
     * @dev The structure is private and used when a new option is created.
     */
    struct BlankOption {
        address option;
        address sniper;
        address bullet;
    }
    BlankOption[] private blankOptions;

    /**
     * @notice The count of blank options in the factory.
     * @dev This public variable is used to keep track of the number of blank options currently stored in the contract.
     *      It's specifically meant to facilitate easy retrieval of the length of the `blankOptions` array.
     */
    uint256 public blankOptionCount;

    /**
     * @notice A mapping of option ID to option contract address.
     * @dev This mapping is private and updated every time a new option is created.
     */
    mapping(uint256 => address) private allOptions;

    /**
     * @notice A mapping of operator address to their whitelisted status.
     * @dev This mapping is public and can be updated by the contract owner.
     */
    mapping(address => bool) public operatorWhitelist;

    /**
     * @notice Event emitted when a new option is created.
     * @dev Contains details about the option, including its ID, contract addresses, strike price, timestamps, and type.
     */
    event OptionCreated(
        uint256 optionID,
        address indexed option,
        address indexed bullet,
        address indexed sniper,
        uint256 strikePrice,
        uint256 startTimestamp,
        uint256 exerciseTimestamp,
        uint256 optionType
    );

    /**
     * @notice Event emitted when the staking reward per block is updated.
     * @dev Contains the old and new reward values.
     */
    event StakingRewardPerBlockChanged(uint256 oldReward, uint256 newReward);

    /**
     * @notice Event emitted when the staking pool address is updated.
     * @dev Contains the old and new staking pool addresses.
     */
    event StakingPoolChanged(address oldStakingPool, address newStakingPool);

    /**
     * @notice Event emitted when the DOB Staking Pool address is updated.
     * @dev Contains the old and new DOB Staking Pool addresses.
     */
    event DOBStakingChanged(address oldDOBStaking, address newDOBStaking);

    /**
     * @notice Event emitted when the distributions address is updated.
     * @dev Contains the old and new distributions addresses.
     */
    event DistribuionsChanged(address oldDDistribuions, address newDistribuions);

    /**
     * @notice Event emitted when the block time is updated.
     * @dev Contains the old and new block time values.
     */
    event BlockTimeChanged(uint8 oldBlockTime, uint8 newBlocktime);

    /**
     * @notice Event emitted when an operator's whitelist status is updated.
     * @dev Contains the operator's address and their new whitelist status.
     */
    event WhitelistChanged(address operator, bool isWhitelisted);

    /**
     * @notice Modifier that requires the caller to be whitelisted.
     * @dev This modifier is used to restrict certain functions to whitelisted operators.
     */
    modifier onlyWhitelist(address _operator) {
        require(operatorWhitelist[_operator], "OptionFactory: Only whitelist");
        _;
    }

    /**
     * @notice Initializes the OptionFactory contract.
     * @dev This is a public function with the initializer modifier, which ensures that it can only be called once, when the contract is first created.
     * - The function first initializes the contract ownership by calling the __Ownable_init function.
     * - It then verifies that the provided bHODL and uHODL addresses are not the zero address.
     * - The function sets the bHODL and uHODL addresses, and the block time, and the staking reward per block.
     * @param _bHodlAddress The address of the bHODL token.
     * @param _uHodlAddress The address of the uHODL token.
     * @custom:error "OptionFactory: zero address" Error thrown if either the bHODL or uHODL address is the zero address.
     */
    function __OptionFactory_init(address _bHodlAddress, address _uHodlAddress) public initializer {
        __Ownable_init();
        require(_bHodlAddress != address(0), "OptionFactory: zero address");
        require(_uHodlAddress != address(0), "OptionFactory: zero address");

        bHODL = _bHodlAddress;
        uHODL = _uHodlAddress;
        blockTime = 12;
        stakingRewardPerBlock = 1.38 * 1e18; // 1.38 DOB per block
    }

    /**
     * @notice Allows the contract owner to adjust the block time.
     * @dev Sets the block time. Must be called by the contract owner.
     * @param _blockTime The block time needed by the system.
     */
    function setBlockTime(uint8 _blockTime) external onlyOwner {
        require(_blockTime > 0, "OptionFactory: zero blockTime");
        uint8 oldBlockTime = blockTime;
        blockTime = _blockTime;
        emit BlockTimeChanged(oldBlockTime, blockTime);
    }

    /**
     * @notice Allows the contract owner to set the address of the staking pools.
     * @dev Sets the staking pools address. Must be called by the contract owner.
     * @param _stakingPools The address of the new staking pools.
     */
    function setStakingPools(address _stakingPools) external onlyOwner {
        require(_stakingPools != address(0), "OptionFactory: zero address");
        address oldStakingPools = stakingPools;
        stakingPools = _stakingPools;
        emit StakingPoolChanged(oldStakingPools, stakingPools);
    }

    /**
     * @notice Allows the contract owner to set the address of the DOB staking pool.
     * @dev Sets the DOB staking pool address. Must be called by the contract owner.
     * @param _DOBstaking The address of the new DOB staking pool.
     */
    function setDOBStakingPool(address _DOBstaking) external onlyOwner {
        require(_DOBstaking != address(0), "OptionFactory: zero address");
        address oldDOBStaking = DOBStakingPool;
        DOBStakingPool = _DOBstaking;
        emit DOBStakingChanged(oldDOBStaking, DOBStakingPool);
    }

    /**
     * @notice Allows the contract owner to manage the list of authorized operators.
     * @dev Adds or removes an operator from the whitelist. Must be called by the contract owner.
     * @param _operator The address of the operator.
     * @param _isWhitelisted A boolean indicating whether the operator is whitelisted.
     */
    function setWhitelist(address _operator, bool _isWhitelisted) public onlyOwner {
        require(_operator != address(0), "Market: Zero address");
        operatorWhitelist[_operator] = _isWhitelisted;
        emit WhitelistChanged(_operator, _isWhitelisted);
    }

    /**
     * @notice Allows the contract owner to set the address of the distributions contract.
     * @dev Sets the distributions address. Must be called by the contract owner.
     * @param _distributions The address of the new distributions contract.
     */
    function setDistributions(address _distributions) external onlyOwner {
        require(_distributions != address(0), "OptionFactory: zero address");
        address oldistributions = distributions;
        distributions = _distributions;
        emit DistribuionsChanged(oldistributions, _distributions);
    }

    /**
     * @notice Allows the contract owner to set the staking reward per block.
     * @dev Sets the reward per block for staking. Must be called by the contract owner.
     * @param _reward The new staking reward per block.
     */
    function setStakingRewardPerBlock(uint256 _reward) external onlyOwner {
        uint256 oldReward = stakingRewardPerBlock;
        stakingRewardPerBlock = _reward;
        emit StakingRewardPerBlockChanged(oldReward, stakingRewardPerBlock);
    }

    /**
     * @notice Allows the contract owner to set the address of the contract generator.
     * @dev Sets the contract generator address. Must be called by the contract owner.
     * @param _ContractGenerator The address of the new contract generator.
     */
    function setContractGenerator(address _ContractGenerator) external onlyOwner {
        require(_ContractGenerator != address(0), "OptionFactory: zero address");
        ContractGenerator = _ContractGenerator;
    }

    /**
     * @notice Allows the contract owner to set the address of the fund.
     * @dev Sets the fund address. Must be called by the contract owner.
     * @param _fund The address of the new fund.
     */
    function setFund(address _fund) external onlyOwner {
        require(_fund != address(0), "OptionFactory: zero address");
        fund = _fund;
    }

    /**
     * @notice Fetches the last option id created
     * @dev Returns the last option id. Does not require ownership or specific permissions to call.
     * @return lastOptionId The ID of the last created option.
     */
    function getLastOptionId() external view override returns (uint256) {
        return lastOptionId;
    }

    /**
     * @notice Fetches the contract address of a particular option by ID
     * @dev Returns the address of the specified option. Does not require ownership or specific permissions to call.
     * @param _optionID The ID of the option.
     * @return address The address of the option.
     */
    function getOptionByID(uint256 _optionID) external view override returns (address) {
        return allOptions[_optionID];
    }

    /**
     * @notice Fetches the address of the staking pools.
     * @dev Returns the address of the staking pools. Does not require ownership or specific permissions to call.
     * @return stakingPools The address of the staking pools.
     */
    function getStakingPools() external view override returns (address) {
        return stakingPools;
    }

    /**
     * @notice Creates a new option contract with specified parameters. Returns the ID of the newly created option.
     * @dev Must be called by an operator from the whitelist. Initializes the option contract, creates tokens,
     *      sets up option details, and creates a pool in the staking contract. Also adds the option to DOBStakingPool.
     * @param _strikePrice The strike price of the new option.
     * @param _startTimestamp The start timestamp of the new option.
     * @param _exerciseTimestamp The exercise timestamp of the new option.
     * @param _optionType The type of the new option (0 for call, 1 for put).
     * @return optionID The ID of the newly created option.
     */
    function createOption(
        uint256 _strikePrice,
        uint256 _startTimestamp,
        uint256 _exerciseTimestamp,
        uint8 _optionType
    ) external override onlyWhitelist(msg.sender) returns (uint256 optionID) {
        require(_strikePrice > 0, "OptionFactory: zero strike price");
        require(_startTimestamp > block.timestamp, "OptionFactory: Illegal start time");
        require(_exerciseTimestamp > _startTimestamp, "OptionFactory: Illegal exercise time");
        require(_optionType <= 1, "OptionFactory: Illegal type");

        address option;
        address bullet;
        address sniper;

        optionID = ++lastOptionId;

        option = IContractGenerator(ContractGenerator).createOptionContract(
            _strikePrice,
            _exerciseTimestamp,
            _optionType,
            address(this)
        );
        IOption(option).initialize(_strikePrice, _exerciseTimestamp, _optionType);
        (bullet, sniper) = IContractGenerator(ContractGenerator).createToken(optionID, option);
        cloneBullet = bullet;
        cloneSniper = sniper;
        cloneOptionId = optionID;
        allOptions[optionID] = option;

        {
            uint256 startTimestamp = _startTimestamp;
            uint256 startBlock = startTimestamp.sub(block.timestamp).div(uint256(blockTime)).add(block.number);
            IOption(option).setup(optionID, startBlock, uHODL, bHODL, fund, bullet, sniper);

            uint256 exerciseTimestamp = _exerciseTimestamp;
            uint256 endBlock = exerciseTimestamp.sub(block.timestamp).div(uint256(blockTime)).add(block.number);
            IStakingPools(stakingPools).createPool(sniper, option, startBlock, endBlock, stakingRewardPerBlock);
        }
        {
            IDOBStakingPool(DOBStakingPool).addOption(option, bullet, sniper);
        }
        {
            uint256 strikePrice = _strikePrice;
            uint256 startTimestamp = _startTimestamp;
            uint256 exerciseTimestamp = _exerciseTimestamp;
            uint8 optionTpye = _optionType;
            emit OptionCreated(optionID, option, bullet, sniper, strikePrice, startTimestamp, exerciseTimestamp, optionTpye);
        }
    }

    /**
     * @notice Clones an existing option a certain number of times.
     * @dev Clones an existing option and creates tokens for the clones. Must be called by an operator from the whitelist.
     * @param _num The number of clones to create.
     */
    function cloneOption(uint8 _num) external onlyWhitelist(msg.sender) {
        require(_num <= 24, "OptionFactory: number must less than 24");
        address bullet;
        address sniper;
        address clone_option = allOptions[cloneOptionId];
        for (uint8 i = 0; i < _num; i++) {
            address option = IContractGenerator(ContractGenerator).cloneOptionPool(clone_option, address(this));
            (bullet, sniper) = IContractGenerator(ContractGenerator).cloneToken(option, cloneBullet, cloneSniper);

            blankOptions.push(BlankOption({option: option, bullet: bullet, sniper: sniper}));
        }
        blankOptionCount = blankOptions.length;
    }

    /**
     * @notice Activates a set of options with specified parameters.
     * @dev Activates options by initializing them, setting them up, and adding them to DOBStakingPool.
     *      Also creates a pool in the staking contract for each option. Must be called by an operator from the whitelist.
     * @param _strikePrices The strike prices of the options to be activated.
     * @param _startTimestamps The start timestamps of the options to be activated.
     * @param _exerciseTimestamps The exercise timestamps of the options to be activated.
     * @param _optionTypes The types of the options to be activated.
     */
    function activateOption(
        uint256[] memory _strikePrices,
        uint256[] memory _startTimestamps,
        uint256[] memory _exerciseTimestamps,
        uint8[] memory _optionTypes
    ) external onlyWhitelist(msg.sender) {
        require(_strikePrices.length == _startTimestamps.length, "OptionFactory: List length not equal");
        require(_startTimestamps.length == _exerciseTimestamps.length, "OptionFactory: List length not equal");
        require(_exerciseTimestamps.length == _optionTypes.length, "OptionFactory: List Length not equal");
        require(_strikePrices.length <= blankOptions.length, "OptionFactory: Insufficient blank Option");

        uint256 arrayLength = _strikePrices.length;
        uint256 optionID;
        uint256 blankOptionsLen = blankOptions.length;

        for (uint8 i = 0; i < arrayLength; i++) {
            uint256 strikePrice = _strikePrices[i];
            uint256 startTimestamp = _startTimestamps[i];
            uint256 exerciseTimestamp = _exerciseTimestamps[i];
            uint8 optionType = _optionTypes[i];

            require(strikePrice > 0, "OptionFactory: zero strike price");
            require(startTimestamp > block.timestamp, "OptionFactory: Illegal start time");
            require(exerciseTimestamp > startTimestamp, "OptionFactory: Illegal exercise time");
            require(optionType <= 1, "OptionFactory: Illegal type");

            BlankOption memory blankOption = blankOptions[blankOptionsLen - i - 1];
            optionID = ++lastOptionId;
            address option = blankOption.option;
            address bullet = blankOption.bullet;
            address sniper = blankOption.sniper;
            IOption(option).initialize(strikePrice, exerciseTimestamp, optionType);

            allOptions[optionID] = option;

            {
                uint256 startBlock = startTimestamp.sub(block.timestamp).div(uint256(blockTime)).add(block.number);
                IOption(option).setup(optionID, startBlock, uHODL, bHODL, fund, bullet, sniper);

                uint256 endBlock = exerciseTimestamp.sub(block.timestamp).div(uint256(blockTime)).add(block.number);
                IStakingPools(stakingPools).createPool(sniper, option, startBlock, endBlock, stakingRewardPerBlock);
            }
            {
                IDOBStakingPool(DOBStakingPool).addOption(option, bullet, sniper);
            }
            {
                emit OptionCreated(
                    optionID,
                    option,
                    bullet,
                    sniper,
                    strikePrice,
                    startTimestamp,
                    exerciseTimestamp,
                    optionType
                );
            }
        }
        for (uint8 i = 0; i < arrayLength; i++) {
            blankOptions.pop();
        }
        blankOptionCount = blankOptions.length;
    }

    /**
     * @notice Updates the strike prices of the specified options.
     * @dev Must be called by an operator from the whitelist.
     * @param _optionIds The IDs of the options to be updated.
     * @param _strikePrices The new strike prices.
     */
    function updateOptionStrike(uint256[] memory _optionIds, uint256[] memory _strikePrices)
        external
        onlyWhitelist(msg.sender)
    {
        require(_optionIds.length == _strikePrices.length, "OptionFactory:List length not equal");
        uint256 arrayLength = _strikePrices.length;
        for (uint8 i = 0; i < arrayLength; i++) {
            uint256 optionId = _optionIds[i];
            uint256 strikePirce = _strikePrices[i];
            address option = allOptions[optionId];
            IOption(option).updateStrike(strikePirce);
        }
    }

    /**
     * @notice Sets the exercise fee ratio for a specific option.
     * @dev The new fee ratio must be in the range [0, 100].maximum ratio is 10%
     * @param optionId The ID of the option.
     * @param _feeRatio The new exercise fee ratio.
     */
    function setOptionExerciseFeeRatio(uint256 optionId, uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio <= 100, "OptionFactory: Illegal value range");
        address option = allOptions[optionId];
        IOption(option).setOptionExerciseFeeRatio(_feeRatio);
    }

    /**
     * @notice Sets the withdraw fee ratio for a specific option.
     * @dev The new fee ratio must be in the range [0, 100].maximum ratio is 10%
     * @param optionId The ID of the option.
     * @param _feeRatio The new withdraw fee ratio.
     */
    function setOptionWithdrawFeeRatio(uint256 optionId, uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio <= 100, "OptionFactory: Illegal value range");
        address option = allOptions[optionId];
        IOption(option).setOptionWithdrawFeeRatio(_feeRatio);
    }

    /**
     * @notice Sets the redeem fee ratio for a specific option.
     * @dev The new fee ratio must be in the range [0, 100].maximum ratio is 10%
     * @param optionId The ID of the option.
     * @param _feeRatio The new redeem fee ratio.
     */
    function setOptionRedeemFeeRatio(uint256 optionId, uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio <= 100, "OptionFactory: Illegal value range");
        address option = allOptions[optionId];
        IOption(option).setOptionRedeemFeeRatio(_feeRatio);
    }

    /**
     * @notice Sets the bullet to reward ratio for a specific option.
     * @dev The new ratio must be in the range [0, 80].
     * @param optionId The ID of the option.
     * @param _feeRatio The new bullet to reward ratio.
     */
    function setOptionBulletToRewardRatio(uint256 optionId, uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio <= 80, "OptionFactory: Illegal value range");
        address option = allOptions[optionId];
        IOption(option).setOptionBulletToRewardRatio(_feeRatio);
    }

    /**
     * @notice Sets the entry fee ratio for a specific option.
     * @dev The new fee ratio must be in the range [0, 100].maximum ratio is 10%
     * @param optionId The ID of the option.
     * @param _feeRatio The new entry fee ratio.
     */
    function setOptionEntryFeeRatio(uint256 optionId, uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio <= 100, "OptionFactory: Illegal value range");
        address option = allOptions[optionId];
        IOption(option).setOptionEntryFeeRatio(_feeRatio);
    }

    /**
     * @notice Extends the end block of the staking pool for a specific option.
     * @param optionId The ID of the option.
     * @param newEndBlock The new end block for the staking pool.
     */
    function extendStakingPoolEndBlock(uint256 optionId, uint256 newEndBlock) public onlyOwner {
        IStakingPools(stakingPools).extendEndBlock(optionId, newEndBlock);
    }

    /**
     * @notice Sets all ratios for a specific option.
     * @dev All ratios must be in their respective legal value ranges.
     * @param optionId The ID of the option.
     * @param _entryFeeRatio The new entry fee ratio.
     * @param _exerciseFeeRatio The new exercise fee ratio.
     * @param _withdrawFeeRatio The new withdraw fee ratio.
     * @param _redeemFeeRatio The new redeem fee ratio.
     * @param _bulletToRewardRatio The new bullet to reward ratio.
     */
    function setOptionAllRatio(
        uint256 optionId,
        uint16 _entryFeeRatio,
        uint16 _exerciseFeeRatio,
        uint16 _withdrawFeeRatio,
        uint16 _redeemFeeRatio,
        uint16 _bulletToRewardRatio
    ) external onlyOwner {
        require(0 <= _entryFeeRatio && _entryFeeRatio <= 100, "Option: entryFeeRatio Illegal value range");
        require(0 <= _exerciseFeeRatio && _exerciseFeeRatio <= 100, "Option: exerciseFeeRatio Illegal value range");
        require(0 <= _withdrawFeeRatio && _withdrawFeeRatio <= 100, "Option: withdrawFeeRatio Illegal value range");
        require(0 <= _redeemFeeRatio && _redeemFeeRatio <= 100, "Option: redeemFeeRatio Illegal value range");
        require(0 <= _bulletToRewardRatio && _bulletToRewardRatio <= 80, "Option: bulletToRewardRatio Illegal value range");
        address option = allOptions[optionId];
        IOption(option).setAllRatio(
            _entryFeeRatio,
            _exerciseFeeRatio,
            _withdrawFeeRatio,
            _redeemFeeRatio,
            _bulletToRewardRatio
        );
    }

    /**
     * @notice Updates the fund address for a list of options.
     * @dev The fund address must not be the zero address.
     * @param optionIds The ID of the option.
     * @param _fund The new fund address.
     */
    function updateOptionFund(uint256[] memory optionIds, address _fund) external onlyOwner {
        require(_fund != address(0), "OptionFactory: zero address");
        for (uint256 i = 0; i < optionIds.length; i++) {
            address option = allOptions[optionIds[i]];
            IOption(option).setFund(_fund);
        }
    }

    /**
     * @notice Removes an activated option from the DOBStakingPool.
     * @dev The option address must not be the zero address.
     * @param _optionAddress The address of the option to remove.
     */
    function removeActivatedOptions(address _optionAddress) external onlyOwner {
        require(_optionAddress != address(0), "OptionFactory: zero address");
        IDOBStakingPool(DOBStakingPool).removeOption(_optionAddress);
    }

    /**
     * @notice Removes all options from the blankOptions array.
     * @dev Iterates through the blankOptions array and removes each element.
     */
    function emptyBlankOptions() external onlyOwner {
        uint256 blankOptionsLen = blankOptions.length;
        for (uint8 i = 0; i < blankOptionsLen; i++) {
            blankOptions.pop();
        }
        blankOptionCount = 0;
    }
}

// File: contracts/interfaces/IOptionFactory.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title IOptionFactory interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for managing option factory contracts
 */
interface IOptionFactory {
    /**
     * @notice Get the ID of the last created option contract
     * @dev Returns the ID of the last created option contract
     * @return The ID of the last created option contract
     */
    function getLastOptionId() external view returns (uint);

    /**
     * @notice Create a new option contract with the specified parameters
     * @dev Creates a new option contract with the specified strike price, start timestamp, exercise timestamp, and option type
     * @param _strikePrice The strike price of the option
     * @param _startTimestamp The start timestamp of the option
     * @param _exerciseTimestamp The exercise timestamp of the option
     * @param _optionType The type of the option (i.e., call or put)
     * @return optionID The ID of the newly created option contract
     */
    function createOption(
        uint256 _strikePrice,
        uint256 _startTimestamp,
        uint256 _exerciseTimestamp,
        uint8 _optionType
    ) external returns (uint256 optionID);

    /**
     * @notice Get the address of the option contract with the specified ID
     * @dev Returns the address of the option contract with the specified ID
     * @param _optionID The ID of the option contract to retrieve the address for
     * @return The address of the option contract
     */
    function getOptionByID(uint256 _optionID) external view returns (address);

    /**
     * @notice Get the address of the staking pool contract
     * @dev Returns the address of the staking pool contract
     * @return The address of the staking pool contract
     */
    function getStakingPools() external view returns (address);

    /**
     * @notice Get the address of the distributions contract
     * @dev Returns the address of the distributions contract
     * @return The address of the distributions contract
     */
    function distributions() external view returns (address);
}

// File: contracts/interfaces/IContractGenerator.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title IContractGenerator interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for generating and cloning token and option contract instances
 */
interface IContractGenerator {
    /**
     * @notice Create a new BULLET and SNIPER token pair for the specified option
     * @dev Generates a new token pair associated with the given option
     * @param optionId The ID of the option associated with the token pair
     * @param optionAddress The address of the option contract
     * @return bullet The address of the newly created BULLET token
     * @return sniper The address of the newly created SNIPER token
     */
    function createToken(uint256 optionId, address optionAddress) external returns (address bullet, address sniper);

    /**
     * @notice Clone existing Bullet and Sniper tokens for a new option contract
     * @dev Creates new token instances by cloning the provided Bullet and Sniper token sources
     * @param _optionAddress The address of the option contract associated with the new tokens
     * @param _bulletSource The address of the source Bullet token to clone
     * @param _sniperSource The address of the source Sniper token to clone
     * @return bullet The address of the newly cloned Bullet token
     * @return sniper The address of the newly cloned Sniper token
     */
    function cloneToken(
        address _optionAddress,
        address _bulletSource,
        address _sniperSource
    ) external returns (address bullet, address sniper);

    /**
     * @notice Clone an existing option pool for a new target address
     * @dev Creates a new option pool instance by cloning the specified target pool
     * @param _targetAddress The address of the target option pool to clone
     * @param _optionFactory The address of the option factory contract
     * @return option The address of the newly cloned option pool
     */
    function cloneOptionPool(address _targetAddress, address _optionFactory) external returns (address option);

    /**
     * @notice Create a new option contract with the specified parameters
     * @dev Generates a new option contract instance with the specified strike price, exercise timestamp, and option type
     * @param _strikePrice The strike price of the new option
     * @param _exerciseTimestamp The exercise timestamp of the new option
     * @param _optionType The type of the new option (i.e., call or put)
     * @param _optionFactory The address of the option factory contract
     * @return option The address of the newly created option contract
     */
    function createOptionContract(
        uint256 _strikePrice,
        uint256 _exerciseTimestamp,
        uint8 _optionType,
        address _optionFactory
    ) external returns (address option);
}

// File: contracts/option/Option.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.4;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "../interfaces/IOptionFactory.sol";
import "../interfaces/IOption.sol";
import "../interfaces/IOptionToken.sol";
import "../interfaces/IStakingPools.sol";
import "../interfaces/IDistributions.sol";

/**
 * @title Option
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice This contract represents an individual option in the DeOrderBook protocol.
 * @dev It handles the option's initialization and all interactions after it has been created.
 */
contract Option is Ownable, ReentrancyGuard, IOption {
    using SafeMath for uint256;

    /**
     * @notice The type of the option (0 = call, 1 = put).
     * @dev This is set when the option is initialized and cannot be changed afterwards.
     */
    uint8 public optionType;

    /**
     * @notice The strike price of the option.
     * @dev This is the price at which the option can be exercised.
     */
    uint256 public strikePrice;

    /**
     * @notice The timestamp at which the option can be exercised.
     * @dev This is set when the option is initialized and cannot be changed afterwards.
     */
    uint256 public exerciseTimestamp;

    /**
     * @notice The unique identifier of the option.
     * @dev This is set when the option is initialized.
     */
    uint256 public optionID;

    /**
     * @notice The block number at which the option starts.
     * @dev This is set when the option is initialized.
     */
    uint256 public startBlock;

    /**
     * @notice The ratio of the entry fee for this option.
     * @dev This is set when the option is initialized and can be updated by the factory.
     */
    uint16 public optionEntryFeeRatio;

    /**
     * @notice The ratio of the exercise fee for this option.
     * @dev This is set when the option is initialized and can be updated by the factory.
     */
    uint16 public optionExerciseFeeRatio;

    /**
     * @notice The ratio of the withdrawal fee for this option.
     * @dev This is set when the option is initialized and can be updated by the factory.
     */
    uint16 public optionWithdrawFeeRatio;

    /**
     * @notice The ratio of the redeem fee for this option.
     * @dev This is set when the option is initialized and can be updated by the factory.
     */
    uint16 public optionRedeemFeeRatio;

    /**
     * @notice The ratio of BULLET to reward for this option.
     * @dev This is set when the option is initialized and can be updated by the factory.
     */
    uint16 public optionBulletToRewardRatio;

    /**
     * @notice The address of the fund.
     * @dev This is set when the option is initialized and can be updated by the factory.
     */
    address public fund;

    /**
     * @notice The address of the option factory.
     * @dev This is set when the option is initialized and cannot be changed afterwards.
     */
    address public optionFactory;

    /**
     * @notice The address of the staking pool.
     * @dev This is set when the option is initialized and cannot be changed afterwards.
     */
    address public stakingPool;

    /**
     * @notice The address of the base token for this option.
     * @dev This is set when the option is initialized and cannot be changed afterwards.
     */
    address private baseToken;

    /**
     * @notice The address of the target token for this option.
     * @dev This is set when the option is initialized and cannot be changed afterwards.
     */
    address private targetToken;

    /**
     * @notice The address of the BULLET token for this option.
     * @dev This is set when the option is initialized and cannot be changed afterwards.
     */
    IOptionToken public bullet;

    /**
     * @notice The address of the SNIPER token for this option.
     * @dev This is set when the option is initialized and cannot be changed afterwards.
     */
    IOptionToken public sniper;

    /**
     * @notice Constant value for preventing precision loss.
     * @dev This is used in various calculations throughout the contract.
     */
    uint256 private constant MULTIPLIER = 10**18;

    /**
     * @notice Emits when a user enters an option.
     * @dev Fired when an account deposits tokens to obtain option tokens.
     * @param optionID The ID of the option entered.
     * @param account The account that made the deposit.
     * @param amount The amount of tokens obtained after fees.
     * @param originalAmount The original amount of tokens entered (before deducting fees).
     */
    event EnteredOption(uint256 optionID, address account, uint256 amount, uint256 originalAmount);

    /**
     * @notice Emits when an option is exercised.
     * @dev Fired when an account exercises their option tokens.
     * @param optionType The type of the option (0 for call, 1 for put).
     * @param optionID The ID of the option.
     * @param timestamp The timestamp when the option was exercised.
     * @param tokenAmount The amount of option tokens exercised.
     * @param hodlAmount The amount of base tokens received upon exercise.
     * @param exerciseFee The fee paid for exercising the option.
     */
    event Exercised(
        uint8 optionType,
        uint256 optionID,
        uint256 timestamp,
        uint256 tokenAmount,
        uint256 hodlAmount,
        uint256 exerciseFee
    );

    /**
     * @notice Emits when option tokens are redeemed.
     * @dev Fired when an account redeems their option tokens after the option's expiration.
     * @param optionType The type of the option (0 for call, 1 for put).
     * @param optionID The ID of the option.
     * @param account The account that redeemed the tokens.
     * @param baseTokenAmount The amount of base tokens redeemed.
     * @param targetTokenAmount The amount of target tokens redeemed.
     * @param originalAmount The original amount of option tokens redeemed (including fees).
     */
    event RedeemedToken(
        uint8 optionType,
        uint256 optionID,
        address account,
        uint256 baseTokenAmount,
        uint256 targetTokenAmount,
        uint256 originalAmount
    );

    /**
     * @notice Emits when target tokens are withdrawn.
     * @dev Fired when an account withdraws their target tokens from the contract.
     * @param optionType The type of the option (0 for call, 1 for put).
     * @param optionID The ID of the option.
     * @param account The account that withdrew the tokens.
     * @param targetTokenAmount The amount of target tokens withdrawn.
     * @param originalTokenAmount The original amount of option tokens burned.
     */
    event WithdrewTarget(
        uint8 optionType,
        uint256 optionID,
        address account,
        uint256 targetTokenAmount,
        uint256 originalTokenAmount
    );

    /**
     * @notice Modifier to restrict calls to the option factory contract.
     * @dev Ensures that only the option factory contract can call certain functions.
     */
    modifier onlyFactory() {
        require(msg.sender == optionFactory, "Option: caller is not the optionFactory");
        _;
    }

    /**
     * @notice Modifier to allow calls only after the option start block.
     * @dev Ensures that certain functions can only be called after the option has started.
     */
    modifier onlyStart() {
        require(block.number >= startBlock, "Option: only after start block");
        _;
    }

    /**
     * @notice Modifier to allow calls only before the option exercise timestamp.
     * @dev Ensures that certain functions can only be called before the option can be exercised.
     */
    modifier onlyBeforeExerciseTime() {
        require(block.timestamp < exerciseTimestamp, "Option: only before exercise time");
        _;
    }

    /**
     * @notice Modifier to allow calls only during the option exercise window.
     * @dev Ensures that certain functions can only be called during the time when the option can be exercised.
     */
    modifier onlyInExerciseTime() {
        require(
            exerciseTimestamp <= block.timestamp && block.timestamp <= exerciseTimestamp + 1 days,
            "Option: only in exercise time"
        );
        _;
    }

    /**
     * @notice Modifier to allow calls only during the option exit window.
     * @dev Ensures that certain functions can only be called after the exercise window has passed.
     */
    modifier onlyExitTime() {
        require(block.timestamp > exerciseTimestamp + 1 days, "Option: only in exit time");
        _;
    }

    /**
     * @notice Initializes the Option contract.
     * @dev Sets the address of the option factory contract.
     * @param _optionFactory The address of the option factory contract.
     */
    constructor(address _optionFactory) {
        optionFactory = _optionFactory;
    }

    /**
     * @notice Set the entry fee ratio for the option.
     * @dev Set the ratio used to calculate the fee for entering the option. The value must be between 0 and 100.
     * @param _feeRatio The new entry fee ratio.
     */
    function setOptionEntryFeeRatio(uint16 _feeRatio) external override onlyFactory {
        require(0 <= _feeRatio && _feeRatio <= 100, "Option: Illegal value range");
        optionEntryFeeRatio = _feeRatio;
    }

    /**
     * @notice Set the exercise fee ratio for the option.
     * @dev Set the ratio used to calculate the fee for exercising the option. The value must be between 0 and 100.
     * @param _feeRatio The new exercise fee ratio.
     */
    function setOptionExerciseFeeRatio(uint16 _feeRatio) external override onlyFactory {
        require(0 <= _feeRatio && _feeRatio <= 100, "Option: Illegal value range");
        optionExerciseFeeRatio = _feeRatio;
    }

    /**
     * @notice Set the withdraw fee ratio for the option.
     * @dev Set the ratio used to calculate the fee for withdrawing tokens from the option. The value must be between 0 and 100.
     * @param _feeRatio The new withdraw fee ratio.
     */
    function setOptionWithdrawFeeRatio(uint16 _feeRatio) external override onlyFactory {
        require(0 <= _feeRatio && _feeRatio <= 100, "Option: Illegal value range");
        optionWithdrawFeeRatio = _feeRatio;
    }

    /**
     * @notice Set the redeem fee ratio for the option.
     * @dev Set the ratio used to calculate the fee for redeeming tokens from the option. The value must be between 0 and 100.
     * @param _feeRatio The new redeem fee ratio.
     */
    function setOptionRedeemFeeRatio(uint16 _feeRatio) external override onlyFactory {
        require(0 <= _feeRatio && _feeRatio <= 100, "Option: Illegal value range");
        optionRedeemFeeRatio = _feeRatio;
    }

    /**
     * @notice Set the BULLET-to-reward ratio for the option.
     * @dev Set the ratio used to calculate the rewards for each BULLET in the option. The value must be between 0 and 80.
     * @param _feeRatio The new BULLET-to-reward ratio.
     */
    function setOptionBulletToRewardRatio(uint16 _feeRatio) external override onlyFactory {
        require(0 <= _feeRatio && _feeRatio <= 80, "Option: Illegal value range");
        optionBulletToRewardRatio = _feeRatio;
    }

    /**
     * @notice Set the fund address for the option.
     * @dev Set the address to which funds related to the option will be sent. The address must not be the zero address.
     * @param _fund The new fund address.
     */
    function setFund(address _fund) external override onlyFactory {
        require(_fund != address(0), "Option: address can not be zero address");
        fund = _fund;
    }

    /**
     * @notice Get the expiry time of the option.
     * @dev Returns the timestamp at which the option will expire.
     * @return The expiry time.
     */
    function getExpiryTime() external view override returns (uint256) {
        return exerciseTimestamp;
    }

    /**
     * @notice Set all the fee ratios for the option.
     * @dev Set the ratios used to calculate the entry, exercise, withdraw and redeem fees, as well as the BULLET-to-reward ratio. The values must be within the appropriate ranges.
     * @param _entryFeeRatio The new entry fee ratio.
     * @param _exerciseFeeRatio The new exercise fee ratio.
     * @param _withdrawFeeRatio The new withdraw fee ratio.
     * @param _redeemFeeRatio The new redeem fee ratio.
     * @param _bulletToRewardRatio The new BULLET-to-reward ratio.
     */
    function setAllRatio(
        uint16 _entryFeeRatio,
        uint16 _exerciseFeeRatio,
        uint16 _withdrawFeeRatio,
        uint16 _redeemFeeRatio,
        uint16 _bulletToRewardRatio
    ) external override onlyFactory {
        if (_entryFeeRatio >= 0 && _entryFeeRatio <= 100) {
            optionEntryFeeRatio = _entryFeeRatio;
        }
        if (_exerciseFeeRatio >= 0 && _exerciseFeeRatio <= 100) {
            optionExerciseFeeRatio = _exerciseFeeRatio;
        }
        if (_withdrawFeeRatio >= 0 && _withdrawFeeRatio <= 100) {
            optionWithdrawFeeRatio = _withdrawFeeRatio;
        }
        if (_redeemFeeRatio >= 0 && _redeemFeeRatio <= 100) {
            optionRedeemFeeRatio = _redeemFeeRatio;
        }
        if (_bulletToRewardRatio >= 0 && _bulletToRewardRatio <= 80) {
            optionBulletToRewardRatio = _bulletToRewardRatio;
        }
    }

    /**
     * @notice Initializes the option contract with provided parameters.
     * @dev Initializes the option with the given strike price, exercise timestamp, and type. This can only be called by the factory.
     * @param _strikePrice The strike price of the option.
     * @param _exerciseTimestamp The exercise timestamp of the option.
     * @param _type The type of the option (0 for call, 1 for put).
     */
    function initialize(
        uint256 _strikePrice,
        uint256 _exerciseTimestamp,
        uint8 _type
    ) external override onlyFactory {
        require(_type <= 1, "OptionFactory: Illegal type");

        strikePrice = _strikePrice;
        exerciseTimestamp = _exerciseTimestamp;
        stakingPool = IOptionFactory(optionFactory).getStakingPools();
        optionType = _type;
    }

    /**
     * @notice Initializes the option contract by cloning it.
     * @dev This function is used to initialize a clone of the option. It sets the option factory address.
     * @param _optionFactory The address of the option factory contract.
     */
    function clone_initialize(address _optionFactory) external {
        require(optionFactory == address(0), "OptionFactory:option is initiated");
        optionFactory = _optionFactory;
    }

    /**
     * @notice Sets up the option contract after initialization with provided parameters.
     * @dev Sets up the option with the given parameters. This can only be called by the factory.
     * @param _optionID The ID of the option.
     * @param _startBlock The start block of the option.
     * @param _uHODLAddress The address of the uHODL token.
     * @param _bHODLTokenAddress The address of the bHODL token.
     * @param _fund The address of the fund.
     * @param _bullet The address of the BULLET token.
     * @param _sniper The address of the SNIPER token.
     */
    function setup(
        uint256 _optionID,
        uint256 _startBlock,
        address _uHODLAddress,
        address _bHODLTokenAddress,
        address _fund,
        address _bullet,
        address _sniper
    ) external override onlyFactory {
        require(_uHODLAddress != address(0), "OptionFactory: zero address");
        require(_bHODLTokenAddress != address(0), "OptionFactory: zero address");
        require(_bullet != address(0), "OptionFactory: zero address");
        require(_sniper != address(0), "OptionFactory: zero address");
        require(_startBlock > block.number, "OptionFactory: Illegal start block");

        optionID = _optionID;
        startBlock = _startBlock;

        // Call option
        if (optionType == 0) {
            baseToken = _uHODLAddress;
            targetToken = _bHODLTokenAddress;
        }
        // Put option
        if (optionType == 1) {
            baseToken = _bHODLTokenAddress;
            targetToken = _uHODLAddress;
        }

        fund = _fund;
        bullet = IOptionToken(_bullet);
        sniper = IOptionToken(_sniper);
        IDistributions distributions = IDistributions(IOptionFactory(optionFactory).distributions());
        optionEntryFeeRatio = distributions.readEntryFeeRatio();
        optionBulletToRewardRatio = distributions.readBulletToRewardRatio();
        optionExerciseFeeRatio = distributions.readExerciseFeeRatio();
        optionWithdrawFeeRatio = distributions.readWithdrawFeeRatio();
        optionRedeemFeeRatio = distributions.readRedeemFeeRatio();

        string memory _newBulletSymbol = getTokenName(strikePrice, exerciseTimestamp, optionType, "Bullet");
        string memory _newSniperSymbol = getTokenName(strikePrice, exerciseTimestamp, optionType, "Sniper");
        bullet.activeInit(optionID, "Bullet", _newBulletSymbol);
        sniper.activeInit(optionID, "Sniper", _newSniperSymbol);
    }

    /**
     * @notice Updates the strike price of the option.
     * @dev Allows updating the strike price before the start block. This can only be called by the factory.
     * @param _strikePrice The new strike price.
     */
    function updateStrike(uint256 _strikePrice) external override onlyFactory {
        require(block.number < startBlock, "Option: after start block, strike cannot update");
        strikePrice = _strikePrice;
        string memory _newBulletSymbol = getTokenName(strikePrice, exerciseTimestamp, optionType, "Bullet");
        string memory _newSniperSymbol = getTokenName(strikePrice, exerciseTimestamp, optionType, "Sniper");
        bullet.updateSymbol(_newBulletSymbol);
        sniper.updateSymbol(_newSniperSymbol);
    }

    /**
     * @notice Enters an options contract by depositing a certain amount of tokens.
     * @dev This function is used to enter an options contract. The sender should have approved the transfer.
     *      The amount of tokens is transferred to this contract, the entry fee is calculated, distributed,
     *      and subtracted from the amount. The remaining amount is used to mint BULLET and SNIPER tokens,
     *      which are passed to the fund and the staking pool, respectively.
     * @param _amount The amount of tokens to enter.
     * @custom:allowance The user may need to approve the contract to spend their target tokens before calling this function.
     */
    function enter(uint256 _amount) external override onlyStart onlyBeforeExerciseTime {
        require(_amount > 0, "Option: zero amount");

        SafeERC20.safeTransferFrom(IERC20(targetToken), msg.sender, address(this), _amount);
        IDistributions distributions = IDistributions(IOptionFactory(optionFactory).distributions());

        uint256 entryFee = _amount.mul(optionEntryFeeRatio).div(10000);
        for (uint8 i = 0; i < distributions.readFeeDistributionLength(); i++) {
            (uint8 percentage, address to) = distributions.readFeeDistribution(i);
            SafeERC20.safeTransfer(IERC20(targetToken), to, entryFee.mul(percentage).div(100));
        }
        _amount = _amount.sub(entryFee);
        uint256 bulletToReward = _amount.mul(optionBulletToRewardRatio).div(100);
        for (uint8 i = 0; i < distributions.readBulletDistributionLength(); i++) {
            (uint8 percentage, address to) = distributions.readBulletDistribution(i);
            bullet.mintFor(to, bulletToReward.mul(percentage).div(100));
        }

        bullet.mintFor(fund, _amount.sub(bulletToReward));

        sniper.mintFor(stakingPool, _amount);
        IStakingPools(stakingPool).stakeFor(optionID, _amount, msg.sender);

        emit EnteredOption(optionID, msg.sender, _amount, _amount.add(entryFee));
    }

    /**
     * @notice Exercises the option by burning option tokens and receiving base tokens.
     * @dev This function burns a specific amount of BULLET tokens and calculates the amount of base tokens
     *      to transfer depending on the option type (call or put). It also calculates and applies the exercise fee.
     * @param _targetAmount The amount of option tokens to exercise.
     * @custom:allowance The user may need to approve the contract to spend their base tokens before calling this function.
     */
    function exercise(uint256 _targetAmount) external override onlyInExerciseTime nonReentrant {
        require(_targetAmount > 0, "Option: zero target amount");
        require(bullet.balanceOf(msg.sender) >= _targetAmount, "Option: not enough BULLET");

        uint256 baseAmount;
        // Call option
        if (optionType == 0) {
            baseAmount = uint256(strikePrice).mul(_targetAmount).div(MULTIPLIER);
        }
        // Put option
        if (optionType == 1) {
            baseAmount = _targetAmount.mul(MULTIPLIER).div(uint256(strikePrice));
        }

        SafeERC20.safeTransferFrom(IERC20(baseToken), msg.sender, address(this), baseAmount);

        bullet.burnFrom(msg.sender, _targetAmount);

        IDistributions distributions = IDistributions(IOptionFactory(optionFactory).distributions());
        uint256 exerciseFee = _targetAmount.mul(optionExerciseFeeRatio).div(10000);
        for (uint8 i = 0; i < distributions.readFeeDistributionLength(); i++) {
            (uint8 percentage, address to) = distributions.readFeeDistribution(i);
            SafeERC20.safeTransfer(IERC20(targetToken), to, exerciseFee.mul(percentage).div(100));
        }
        SafeERC20.safeTransfer(IERC20(targetToken), msg.sender, _targetAmount.sub(exerciseFee));

        emit Exercised(optionType, optionID, block.timestamp, _targetAmount, baseAmount, exerciseFee);
    }

    /**
     * @notice Exits the option by unstaking and redeeming all rewards.
     * @dev This function unstakes the user's tokens, redeems their SNIPER tokens, and withdraws their rewards.
     */
    function exitAll() external override onlyExitTime {
        uint256 stakingAmount = IStakingPools(stakingPool).getStakingAmountByPoolID(msg.sender, optionID);
        if (stakingAmount > 0) {
            IStakingPools(stakingPool).unstakeFor(optionID, stakingAmount, msg.sender);
        }
        uint256 totalSniperAmount = sniper.balanceOf(msg.sender);
        redeemToken(totalSniperAmount);
        IStakingPools(stakingPool).redeemRewardsByAddress(optionID, msg.sender);
    }

    /**
     * @notice Unwinds a specific amount of options.
     * @dev
     * - The function unwinds a specific amount of options before the exercise time.
     * - It first checks if the user has enough BULLET tokens.
     * - It then ensures that the user has a sufficient amount of SNIPER tokens and staked SNIPER tokens.
     * - If the user doesn't have enough SNIPER tokens, the function unstakes the required SNIPER tokens from the StakingPool.
     * - The function then redeems any rewards available for the user in the StakingPool.
     * - Finally, it withdraws the target amount on behalf of the user.
     * @param _unwindAmount The amount of options to unwind.
     * @custom:error "Option: not enough BULLET" Error thrown if the user doesn't have enough BULLET tokens.
     * @custom:error "Option: not enough staking amount" Error thrown if the user doesn't have enough staked SNIPER tokens and SNIPER token balance.
     * @custom:event RewardRedeemed Emits if rewards were redeemed.
     * @custom:event TargetWithdrawn Emits if the target amount was withdrawn successfully.
     */
    function unwind(uint256 _unwindAmount) external override onlyBeforeExerciseTime {
        uint256 bulletAmount = bullet.balanceOf(msg.sender);
        require(_unwindAmount <= bulletAmount, "Option: not enough BULLET");

        uint256 _sniperAmount = sniper.balanceOf(msg.sender);
        uint256 _stakingAmount = IStakingPools(stakingPool).getStakingAmountByPoolID(msg.sender, optionID);

        require((_stakingAmount.add(_sniperAmount)) >= _unwindAmount, "Option: not enough staking amount");

        if (_unwindAmount > _sniperAmount) {
            IStakingPools(stakingPool).unstakeFor(optionID, _unwindAmount.sub(_sniperAmount), msg.sender);
        }

        IStakingPools(stakingPool).redeemRewardsByAddress(optionID, msg.sender);
        withdrawTarget(_unwindAmount);
    }

    /**
     * @notice Unwinds the specified amount from the target token.
     * @dev The user burns a certain amount of SNIPER and BULLET tokens to unwind a corresponding amount of target tokens.
     *      A fee (withdrawFee) is also calculated and deducted from the unwound amount.
     * @param _amount The amount of target tokens to unwind.
     */
    function withdrawTarget(uint256 _amount) public onlyBeforeExerciseTime nonReentrant {
        require(sniper.balanceOf(msg.sender) >= _amount, "Option: not enough SNIPER");
        require(bullet.balanceOf(msg.sender) >= _amount, "Option: not enough BULLET");

        sniper.burnFrom(msg.sender, _amount);
        bullet.burnFrom(msg.sender, _amount);

        IDistributions distributions = IDistributions(IOptionFactory(optionFactory).distributions());
        uint256 withdrawFee = _amount.mul(optionWithdrawFeeRatio).div(10000);
        for (uint8 i = 0; i < distributions.readFeeDistributionLength(); i++) {
            (uint8 percentage, address to) = distributions.readFeeDistribution(i);
            SafeERC20.safeTransfer(IERC20(targetToken), to, withdrawFee.mul(percentage).div(100));
        }
        SafeERC20.safeTransfer(IERC20(targetToken), msg.sender, _amount.sub(withdrawFee));

        emit WithdrewTarget(optionType, optionID, msg.sender, _amount.sub(withdrawFee), _amount);
    }

    /**
     * @notice Redeems SNIPER tokens after the option has been exercised.
     * @dev This function calculates the amount of base and target tokens to redeem by proportionally dividing
     *      the total balance of these tokens by the total supply of SNIPER tokens. The redeem fees are also calculated.
     *      It then transfers the redeemed amounts to the user and burns their SNIPER tokens.
     * @param _amount The amount of SNIPER tokens to redeem.
     */
    function redeemToken(uint256 _amount) internal nonReentrant {
        require(_amount > 0, "Option: zero amount");
        require(_amount <= sniper.balanceOf(msg.sender), "Option: not enough SNIPER");

        uint256 totalBaseToken = IERC20(baseToken).balanceOf(address(this));
        uint256 totalTargetToken = IERC20(targetToken).balanceOf(address(this));
        uint256 totalSupplyOfSniper = sniper.totalSupply();

        uint256 baseTokenAmount = _amount.mul(totalBaseToken).div(totalSupplyOfSniper);
        uint256 targetTokenAmount = _amount.mul(totalTargetToken).div(totalSupplyOfSniper);

        sniper.burnFrom(msg.sender, _amount);

        IDistributions distributions = IDistributions(IOptionFactory(optionFactory).distributions());
        uint256 baseRedeemFee = baseTokenAmount.mul(optionRedeemFeeRatio).div(10000);
        uint256 targetRedeemFee = targetTokenAmount.mul(optionRedeemFeeRatio).div(10000);
        if (baseTokenAmount > 0) {
            for (uint8 i = 0; i < distributions.readFeeDistributionLength(); i++) {
                (uint8 percentage, address to) = distributions.readFeeDistribution(i);
                SafeERC20.safeTransfer(IERC20(baseToken), to, baseRedeemFee.mul(percentage).div(100));
            }
            SafeERC20.safeTransfer(IERC20(baseToken), msg.sender, baseTokenAmount.sub(baseRedeemFee));
        }

        if (targetTokenAmount > 0) {
            for (uint8 i = 0; i < distributions.readFeeDistributionLength(); i++) {
                (uint8 percentage, address to) = distributions.readFeeDistribution(i);
                SafeERC20.safeTransfer(IERC20(targetToken), to, targetRedeemFee.mul(percentage).div(100));
            }
            SafeERC20.safeTransfer(IERC20(targetToken), msg.sender, targetTokenAmount.sub(targetRedeemFee));
        }
        emit RedeemedToken(
            optionType,
            optionID,
            msg.sender,
            baseTokenAmount.sub(baseRedeemFee),
            targetTokenAmount.sub(targetRedeemFee),
            _amount
        );
    }

    /**
     * @notice Gets the current name for a token
     * @dev This helper function concatenates the provided parameters into a string,
     *      formatted to represent a token's name
     * @param _strikePrice The strike price of the option the token is associated with
     * @param _exerciseTimestamp The expiration time of the option the token is associated with
     * @param _optionType The type of option the token is associated with (0 = Call, 1 = Put)
     * @param _tokenType The type of token ("Bullet" or "Sniper")
     * @return _name The formatted name of the token
     */
    function getTokenName(
        uint256 _strikePrice,
        uint256 _exerciseTimestamp,
        uint8 _optionType,
        string memory _tokenType
    ) private pure returns (string memory) {
        string memory tokenPrefix = "";
        if (_optionType == 0) {
            tokenPrefix = "b";
        }
        if (_optionType == 1) {
            tokenPrefix = "u";
        }
        return
            string(
                abi.encodePacked(
                    tokenPrefix,
                    _tokenType,
                    "(",
                    Strings.toString(_exerciseTimestamp),
                    "-",
                    Strings.toString(_strikePrice.div(MULTIPLIER)),
                    ")"
                )
            );
    }
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _setOwner(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _setOwner(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _setOwner(newOwner);
    }

    function _setOwner(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC20.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));
        }
    }

    /**
     * @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/utils/math/SafeMath.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

// File: @openzeppelin/contracts/security/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

// File: contracts/interfaces/IOptionToken.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title IOptionToken interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for managing option token contracts
 */
interface IOptionToken {
    /**
     * @notice Get the total supply of option tokens
     * @dev Returns the total supply of option tokens
     * @return The total supply of option tokens
     */
    function totalSupply() external view returns (uint256);

    /**
     * @notice Get the balance of the specified account
     * @dev Returns the balance of the specified account
     * @param account The account to retrieve the balance for
     * @return The balance of the specified account
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @notice Mint new option tokens and assign them to the specified account
     * @dev Mints new option tokens and assigns them to the specified account
     * @param _account The account to assign the new tokens to
     * @param _amount The amount of new tokens to mint
     */
    function mintFor(address _account, uint256 _amount) external;

    /**
     * @notice Burn the specified amount of option tokens
     * @dev Burns the specified amount of option tokens from the caller's account
     * @param amount The amount of tokens to burn
     */
    function burn(uint256 amount) external;

    /**
     * @notice Burn the specified amount of option tokens from the specified account
     * @dev Burns the specified amount of option tokens from the specified account
     * @param account The account to burn the tokens from
     * @param amount The amount of tokens to burn
     */
    function burnFrom(address account, uint256 amount) external;

    /**
     * @notice Update the symbol of the option token
     * @dev Updates the symbol of the option token to the specified value
     * @param _new_symbol The new symbol of the option token
     */
    function updateSymbol(string memory _new_symbol) external;

    /**
     * @notice Activate the option token with the specified parameters
     * @dev Activates the option token with the specified option ID, name, and symbol
     * FIXME: should "_optionID" below just be "optionID" if it's set when the option is created
     * @param _optionID The ID of the option contract
     * @param _new_name The new name of the option token
     * @param _new_symbol The new symbol of the option token
     */
    function activeInit(uint256 _optionID, string memory _new_name, string memory _new_symbol) external;
}

// File: contracts/interfaces/IDistributions.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;

/**
 * @title IDistributions interface
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev Interface for managing fee and reward distributions
 */
interface IDistributions {
    /**
     * @notice Get the entry fee ratio
     * @dev Returns the current entry fee ratio in basis points (i.e., parts per 10,000)
     * @return The current entry fee ratio
     */
    function readEntryFeeRatio() external view returns (uint16);

    /**
     * @notice Get the exercise fee ratio
     * @dev Returns the current exercise fee ratio in basis points (i.e., parts per 10,000)
     * @return The current exercise fee ratio
     */
    function readExerciseFeeRatio() external view returns (uint16);

    /**
     * @notice Get the withdraw fee ratio
     * @dev Returns the current withdraw fee ratio in basis points (i.e., parts per 10,000)
     * @return The current withdraw fee ratio
     */
    function readWithdrawFeeRatio() external view returns (uint16);

    /**
     * @notice Get the redeem fee ratio
     * @dev Returns the current redeem fee ratio in basis points (i.e., parts per 10,000)
     * @return The current redeem fee ratio
     */
    function readRedeemFeeRatio() external view returns (uint16);

    /**
     * @notice Get the BULLET-to-reward ratio
     * @dev Returns the current BULLET-to-reward ratio in percentage points (i.e., parts per 100)
     * @return The current BULLET-to-reward ratio
     */
    function readBulletToRewardRatio() external view returns (uint16);

    /**
     * @notice Get the number of fee distribution targets
     * @dev Returns the number of fee distribution targets
     * @return The number of fee distribution targets
     */
    function readFeeDistributionLength() external view returns (uint256);

    /**
     * @notice Get the fee distribution target at the specified index
     * @dev Returns the fee distribution target at the specified index
     * @param i The index of the fee distribution target to retrieve
     * @return The fee distribution target at the specified index as a tuple (fee ratio, target address)
     */
    function readFeeDistribution(uint256 i) external view returns (uint8, address);

    /**
     * @notice Get the number of BULLET distribution targets
     * @dev Returns the number of BULLET distribution targets
     * @return The number of BULLET distribution targets
     */
    function readBulletDistributionLength() external view returns (uint256);

    /**
     * @notice Get the BULLET distribution target at the specified index
     * @dev Returns the BULLET distribution target at the specified index
     * @param i The index of the BULLET distribution target to retrieve
     * @return The BULLET distribution target at the specified index as a tuple (distribution ratio, target address)
     */
    function readBulletDistribution(uint256 i) external view returns (uint8, address);
}

// File: contracts/option/Distributions.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "../interfaces/IDistributions.sol";

/**
 * @title Distributions
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice This contract manages different types of fees and their distributions. It is responsible for
 *         defining fee ratios and their allocations. It is upgradeable and only the contract owner has
 *         the permission to change these values. The types of fees include Entry, Exercise, Withdraw,
 *         Redeem, and HODL Withdraw fees. It also manages the ratio of Bullet to Reward and the
 *         distributions of these fees and bullet rewards.
 * @dev This contact uses the concept of "ratio" for managing fee ratios and uses an array of Distribution structs for allocating these fees. The
 *      Distribution struct has two properties: percentage and recipient's address. The contract emits
 *      various events when fee ratios or distributions are changed.
 */
contract Distributions is OwnableUpgradeable {
    /**
     * @notice The ratio of the entry fee for a DeOrder.
     * @dev Represents the percentage of the fee taken when a new DeOrder is created. Values are in basis points, so a value of 100 means 1%.
     */
    uint16 public entryFeeRatio;

    /**
     * @notice The ratio of the exercise fee.
     * @dev Represents the percentage of the fee taken when a DeOrder is exercised. Values are in basis points, so a value of 100 means 1%.
     */
    uint16 public exerciseFeeRatio;

    /**
     * @notice The ratio of the withdrawal fee when collecting from a DeOrder.
     * @dev Represents the percentage of the fee taken when funds are withdrawn from a DeOrder. Values are in basis points, so a value of 100 means 1%.
     */
    uint16 public withdrawFeeRatio;

    /**
     * @notice The ratio of the redeem fee.
     * @dev Represents the percentage of the fee taken when a DeOrder is redeemed. Values are in basis points, so a value of 100 means 1%.
     */
    uint16 public redeemFeeRatio;

    /**
     * @notice The ratio of bullet to reward.
     * @dev This is used to calculate rewards from bullets. For example, a value of 80 means for every 1 bullet, 0.8 rewards are given.
     */
    uint8 public bulletToRewardRatio;

    /**
     * @notice The ratio of HODL withdrawal fee.
     * @dev Represents the percentage of the fee taken when funds are withdrawn from a HODL. Values are in basis points, so a value of 100 means 1%.
     */
    uint16 public hodlWithdrawFeeRatio;

    /**
     * @notice Represents a fee distribution.
     * @dev A struct representing a fee distribution, containing the percentage of the fee and the address to which it should be distributed.
     */
    struct Distribution {
        uint8 percentage;
        address to;
    }

    /**
     * @notice An array representing the fee distribution.
     * @dev An array of Distribution structs representing how the fee is distributed among multiple addresses.
     */
    Distribution[] public feeDistribution;

    /**
     * @notice An array representing the bullet distribution.
     * @dev An array of Distribution structs representing how the bullet rewards are distributed among multiple addresses.
     */
    Distribution[] public bulletDistribution;

    /**
     * @notice An array representing the HODL withdrawal fee distribution.
     * @dev An array of Distribution structs representing how the HODL withdrawal fee is distributed among multiple addresses.
     */
    Distribution[] public hodlWithdrawFeeDistribution;

    /**
     * @notice The length of the fee distribution array.
     * @dev The current length (i.e., the number of recipients) of the fee distribution array.
     */
    uint256 public feeDistributionLength;

    /**
     * @notice The length of the bullet distribution array.
     * @dev The current length (i.e., the number of recipients) of the bullet distribution array.
     */
    uint256 public bulletDistributionLength;

    /**
     * @notice The length of the HODL withdrawal fee distribution array.
     * @dev The current length (i.e., the number of recipients) of the HODL withdrawal fee distribution array.
     */
    uint256 public hodlWithdrawFeeDistributionLength;

    /**
     * @notice Emitted when the entry fee ratio is updated.
     * @dev This event triggers when the existing entry fee ratio changes to a new value.
     * @param oldEntryFeeRatio The old entry fee ratio.
     * @param newEntryFeeRatio The new entry fee ratio.
     */
    event EntryFeeRatioChanged(uint16 oldEntryFeeRatio, uint16 newEntryFeeRatio);

    /**
     * @notice Emitted when the exercise fee ratio is updated.
     * @dev This event triggers when the existing exercise fee ratio changes to a new value.
     * @param oldExerciseFeeRatio The old exercise fee ratio.
     * @param newExerciseFeeRatio The new exercise fee ratio.
     */
    event ExerciseFeeRatioChanged(uint16 oldExerciseFeeRatio, uint16 newExerciseFeeRatio);

    /**
     * @notice Emitted when the withdraw fee ratio is updated.
     * @dev This event triggers when the existing withdraw fee ratio changes to a new value.
     * @param oldWithdrawFeeRatio The old withdraw fee ratio.
     * @param newWithdrawFeeRatio The new withdraw fee ratio.
     */
    event WithdrawFeeRatioChanged(uint16 oldWithdrawFeeRatio, uint16 newWithdrawFeeRatio);

    /**
     * @notice Emitted when the redeem fee ratio is updated.
     * @dev This event triggers when the existing redeem fee ratio changes to a new value.
     * @param oldRedeemFeeRatio The old redeem fee ratio.
     * @param newRedeemFeeRatio The new redeem fee ratio.
     */
    event RedeemFeeRatioChanged(uint16 oldRedeemFeeRatio, uint16 newRedeemFeeRatio);

    /**
     * @notice Emitted when the HODL withdraw fee ratio is updated.
     * @dev This event triggers when the existing HODL withdraw fee ratio changes to a new value.
     * @param oldHodlWithdrawFeeRatio The old HODL withdraw fee ratio.
     * @param newHodlWithdrawFeeRatio The new HODL withdraw fee ratio.
     */
    event HodlWithdrawFeeRatioChanged(uint16 oldHodlWithdrawFeeRatio, uint16 newHodlWithdrawFeeRatio);

    /**
     * @notice Emitted when the bullet-to-reward ratio is updated.
     * @dev This event triggers when the existing bullet-to-reward ratio changes to a new value.
     * @param oldBulletToRewardRatio The old bullet-to-reward ratio.
     * @param newBulletToRewardRatio The new bullet-to-reward ratio.
     */
    event BulletToRewardRatioChanged(uint8 oldBulletToRewardRatio, uint8 newBulletToRewardRatio);

    /**
     * @notice Emitted when the fee distribution is updated.
     * @dev This event triggers when the fee distribution list is changed. It includes the updated percentages and recipient addresses.
     * @param percentage The array of fee distribution percentages.
     * @param to The array of fee distribution recipients.
     */
    event FeeDistributionSet(uint8[] percentage, address[] to);

    /**
     * @notice Emitted when the bullet distribution is updated.
     * @dev This event triggers when the bullet distribution list is changed. It includes the updated percentages and recipient addresses.
     * @param percentage The array of bullet distribution percentages.
     * @param to The array of bullet distribution recipients.
     */
    event BulletDistributionSet(uint8[] percentage, address[] to);

    /**
     * @notice Emitted when the HODL withdraw fee distribution is updated.
     * @dev This event triggers when the HODL withdraw fee distribution list is changed. It includes the updated percentages and recipient addresses.
     * @param percentage The array of HODL withdraw fee distribution percentages.
     * @param to The array of HODL withdraw fee distribution recipients.
     */
    event HodlWithdrawFeeDistributionSet(uint8[] percentage, address[] to);

    /**
     * @notice Initializes the Distributions contract.
     * @dev Invokes the initialization function of the parent contract and sets the bulletToRewardRatio to 80.
     */
    function __Distributions_init() public initializer {
        __Ownable_init();
        bulletToRewardRatio = 80;
        exerciseFeeRatio = 20;
        withdrawFeeRatio = 20;
        hodlWithdrawFeeRatio = 20;
        redeemFeeRatio = 20;
        entryFeeRatio = 20;
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the exercise fee ratio.
     * @param _feeRatio The new exercise fee ratio.
     */
    function setExerciseFee(uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio < 10000, "Distributions: Illegal value range");

        uint16 oldFeeRatio = exerciseFeeRatio;
        exerciseFeeRatio = _feeRatio;
        emit ExerciseFeeRatioChanged(oldFeeRatio, exerciseFeeRatio);
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the withdraw fee ratio.
     * @param _feeRatio The new withdraw fee ratio.
     */
    function setWithdrawFee(uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio < 10000, "Distributions: Illegal value range");

        uint16 oldFeeRatio = withdrawFeeRatio;
        withdrawFeeRatio = _feeRatio;
        emit WithdrawFeeRatioChanged(oldFeeRatio, withdrawFeeRatio);
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the redeem fee ratio.
     * @param _feeRatio The new redeem fee ratio.
     */
    function setRedeemFee(uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio < 10000, "Distributions: Illegal value range");

        uint16 oldFeeRatio = redeemFeeRatio;
        redeemFeeRatio = _feeRatio;
        emit RedeemFeeRatioChanged(oldFeeRatio, redeemFeeRatio);
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the HODL withdraw fee ratio.
     * @param _feeRatio The new HODL withdraw fee ratio.
     */
    function setHodlWithdrawFee(uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio < 10000, "Distributions: Illegal value range");

        uint16 oldFeeRatio = hodlWithdrawFeeRatio;
        hodlWithdrawFeeRatio = _feeRatio;
        emit HodlWithdrawFeeRatioChanged(oldFeeRatio, hodlWithdrawFeeRatio);
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the bullet-to-reward ratio.
     * @param _bulletToRewardRatio The new bullet-to-reward ratio.
     */
    function setBulletToRewardRatio(uint8 _bulletToRewardRatio) external onlyOwner {
        require(0 <= _bulletToRewardRatio && _bulletToRewardRatio <= 80, "Distributions: Illegal value range");

        uint8 oldBulletToRewardRatio = bulletToRewardRatio;
        bulletToRewardRatio = _bulletToRewardRatio;
        emit BulletToRewardRatioChanged(oldBulletToRewardRatio, bulletToRewardRatio);
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the entry fee ratio.
     * @param _feeRatio The new entry fee ratio.
     */
    function setEntryFee(uint16 _feeRatio) external onlyOwner {
        require(0 <= _feeRatio && _feeRatio < 10000, "Distributions: Illegal value range");

        uint16 oldFeeRatio = entryFeeRatio;
        entryFeeRatio = _feeRatio;
        emit EntryFeeRatioChanged(oldFeeRatio, entryFeeRatio);
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the fee distribution percentages and recipients.
     * @param _percentage The array of fee distribution percentages.
     * @param _to The array of fee distribution recipients.
     */
    function setFeeDistribution(uint8[] memory _percentage, address[] memory _to) external onlyOwner {
        require(_percentage.length == _to.length, "Distributions: Array length does not match");
        uint8 sum;
        for (uint8 i = 0; i < _percentage.length; i++) {
            sum += _percentage[i];
        }
        require(sum == 100, "Distributions: Sum of percentages is not 100");
        delete feeDistribution;
        for (uint8 j = 0; j < _percentage.length; j++) {
            uint8 percentage = _percentage[j];
            address to = _to[j];
            Distribution memory distribution = Distribution({percentage: percentage, to: to});
            feeDistribution.push(distribution);
        }
        feeDistributionLength = _percentage.length;
        emit FeeDistributionSet(_percentage, _to);
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the bullet distribution percentages and recipients.
     * @param _percentage The array of bullet distribution percentages.
     * @param _to The array of bullet distribution recipients.
     */
    function setBulletDistribution(uint8[] memory _percentage, address[] memory _to) external onlyOwner {
        require(_percentage.length == _to.length, "Distributions: Array length does not match");
        uint8 sum;
        for (uint8 i = 0; i < _percentage.length; i++) {
            sum += _percentage[i];
        }
        require(sum == 100, "Distributions: Sum of percentages is not 100");
        delete bulletDistribution;
        for (uint8 j = 0; j < _percentage.length; j++) {
            uint8 percentage = _percentage[j];
            address to = _to[j];
            Distribution memory distribution = Distribution({percentage: percentage, to: to});
            bulletDistribution.push(distribution);
        }
        bulletDistributionLength = _percentage.length;
        emit BulletDistributionSet(_percentage, _to);
    }

    /**
     * @notice Only the contract owner can call this function.
     * @dev Sets the HODL withdraw fee distribution percentages and recipients.
     * @param _percentage The array of HODL withdraw fee distribution percentages.
     * @param _to The array of HODL withdraw fee distribution recipients.
     */
    function setHodlWithdrawFeeDistribution(uint8[] memory _percentage, address[] memory _to) external onlyOwner {
        require(_percentage.length == _to.length, "Distributions: Array length does not match");
        uint8 sum;
        for (uint8 i = 0; i < _percentage.length; i++) {
            sum += _percentage[i];
        }
        require(sum == 100, "Distributions: Sum of percentages is not 100");
        delete hodlWithdrawFeeDistribution;
        for (uint8 j = 0; j < _percentage.length; j++) {
            uint8 percentage = _percentage[j];
            address to = _to[j];
            Distribution memory distribution = Distribution({percentage: percentage, to: to});
            hodlWithdrawFeeDistribution.push(distribution);
        }
        hodlWithdrawFeeDistributionLength = _percentage.length;
        emit HodlWithdrawFeeDistributionSet(_percentage, _to);
    }

    /**
     * @notice Get the current entry fee ratio.
     * @dev Provides access to the value of the `entryFeeRatio` state variable.
     * @return The entry fee ratio.
     */
    function readEntryFeeRatio() public view returns (uint16) {
        return entryFeeRatio;
    }

    /**
     * @notice Get the current exercise fee ratio.
     * @dev Provides access to the value of the `exerciseFeeRatio` state variable.
     * @return The exercise fee ratio.
     */
    function readExerciseFeeRatio() public view returns (uint16) {
        return exerciseFeeRatio;
    }

    /**
     * @notice Get the current withdrawal fee ratio.
     * @dev Provides access to the value of the `withdrawFeeRatio` state variable.
     * @return The withdraw fee ratio.
     */
    function readWithdrawFeeRatio() public view returns (uint16) {
        return withdrawFeeRatio;
    }

    /**
     * @notice Get the current redeem fee ratio.
     * @dev Provides access to the value of the `redeemFeeRatio` state variable.
     * @return The redeem fee ratio.
     */
    function readRedeemFeeRatio() public view returns (uint16) {
        return redeemFeeRatio;
    }

    /**
     * @notice Get the current bullet-to-reward ratio.
     * @dev Provides access to the value of the `bulletToRewardRatio` state variable.
     * @return The bullet-to-reward ratio.
     */
    function readBulletToRewardRatio() public view returns (uint16) {
        return bulletToRewardRatio;
    }

    /**
     * @notice Get the current length of the fee distribution array.
     * @dev Provides access to the value of the `feeDistributionLength` state variable.
     * @return The length of the fee distribution array.
     */
    function readFeeDistributionLength() public view returns (uint256) {
        return feeDistributionLength;
    }

    /**
     * @notice Get the fee distribution at the given index.
     * @dev Provides access to the `feeDistribution` array at a given index `i`.
     * @param i The index of the fee distribution.
     * @return percentage The percentage of the fee distribution.
     * @return to The recipient of the fee distribution.
     */
    function readFeeDistribution(uint256 i) public view returns (uint8 percentage, address to) {
        percentage = feeDistribution[i].percentage;
        to = feeDistribution[i].to;
    }

    /**
     * @notice Get the current length of the bullet distribution array.
     * @dev Provides access to the value of the `bulletDistributionLength` state variable.
     * @return The length of the bullet distribution array.
     */
    function readBulletDistributionLength() public view returns (uint256) {
        return bulletDistributionLength;
    }

    /**
     * @notice Get the bullet distribution at the given index.
     * @dev Provides access to the `bulletDistribution` array at a given index `i`.
     * @param i The index of the bullet distribution.
     * @return percentage The percentage of the bullet distribution.
     * @return to The recipient of the bullet distribution.
     */
    function readBulletDistribution(uint256 i) public view returns (uint8 percentage, address to) {
        percentage = bulletDistribution[i].percentage;
        to = bulletDistribution[i].to;
    }
}

// File: contracts/token/HODLToken.sol



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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../option/Distributions.sol";

/**
 * @title HODLToken
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice HODL tokens are wrappers for external tokens designed to incentivize holders to retain their position by offering special functionality, like the ability to check if a holder owns a certain NFT.
 * @dev Inherits from ERC20Wrapper and Ownable. Uses the SafeMath library for all mathematical operations to prevent overflow and underflow errors.
 */
contract HODLToken is ERC20Wrapper, Ownable {
    using SafeMath for uint256;

    /**
     * @notice An instance of the Distributions contract.
     * @dev This contract is responsible for managing the distribution of fees collected from HODL tokens.
     */
    Distributions public immutable distributions;

    /**
     * @notice A flag indicating whether NFT ownership checks are enabled or disabled.
     * @dev If true, the contract will check if a token holder owns a certain NFT before allowing certain operations.
     */
    bool public isCheckNFT;

    /**
     * @notice The address of the ERC721 token contract against which NFT ownership checks are to be performed.
     * @dev This address is set via the `setIsCheckNFT` function and is used in the `nftCheckSuccess` function.
     */
    ERC721 public NFTAddress;
    /**
     * @notice The conversion rate used to determine the number of HODL tokens minted in exchange for the underlying token.
     * @dev This rate is applied when depositing the underlying token to mint HODL tokens, essentially defining the ratio between the underlying token and HODL token.
     * It's specified as a power of 10, meaning that a conversion rate of N would lead to a conversion ratio of 10^N.
     */
    uint256 public conversionRate;

    /**
     * @notice Constructs the HODLToken contract.
     * @dev Assigns the underlying ERC20 token that the HODL token wraps and the Distributions contract.
     * @param underlyingTokenAddress The address of the ERC20 token that the HODL token wraps.
     * @param distributionsAddress The address of the Distributions contract.
     * @param symbol_ The symbol of the HODL token.
     * @param _conversionRate The conversion rate of the HODL token.
     */
    constructor(
        address underlyingTokenAddress,
        address distributionsAddress,
        string memory symbol_,
        uint256 _conversionRate
    ) ERC20("HODL Token", symbol_) ERC20Wrapper(IERC20(underlyingTokenAddress)) {
        require(underlyingTokenAddress != address(0), "HODLToken: zero address");
        distributions = Distributions(distributionsAddress);
        conversionRate = _conversionRate;
    }

    /**
     * @notice Allows the contract owner to enable or disable NFT ownership checks and to set the NFT contract address.
     * @dev If NFT checks are enabled, the provided NFT contract address must be a valid contract address.
     * @param _isCheckNFT Indicates whether NFT checks are to be enabled or disabled.
     * @param _nftAddress The address of the NFT contract against which checks are to be performed.
     */
    function setIsCheckNFT(bool _isCheckNFT, ERC721 _nftAddress) public onlyOwner {
        if (_isCheckNFT) {
            require(address(_nftAddress) != address(0), "HODLToken: NFT zero address");
            uint256 size;
            assembly {
                size := extcodesize(_nftAddress)
            }
            require(size > 0, "Not a contract");
            NFTAddress = _nftAddress;
        }
        isCheckNFT = _isCheckNFT;
    }

    /**
     * @notice Checks whether the caller owns an NFT, if NFT checks are enabled.
     * @dev Returns true if NFT checks are disabled or if the caller owns an NFT.
     * @return Returns true if NFT checks are disabled or if the caller owns an NFT.
     */
    function nftCheckSuccess() private view returns (bool) {
        if (isCheckNFT) {
            uint256 userNft = NFTAddress.balanceOf(msg.sender);
            if (userNft > 0) {
                return true;
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

    /**
     * @notice Allows a user to deposit a specified amount of the underlying token and mints them an equivalent amount of HODL tokens.
     * @dev The sender must own an NFT if NFT checks are enabled. The sender cannot be the zero address.
     * @param amount The amount of the underlying token to deposit.
     * @return Returns true upon success.
     */
    function deposit(uint256 amount) external returns (bool) {
        depositFor(msg.sender, amount);
        return true;
    }

    /**
     * @notice Allows an account to deposit a specified amount of the underlying token on behalf of another address and mints them an equivalent amount of HODL tokens.
     * @dev The underlying token is transferred from the sender's address to the contract, and HODL tokens are minted to the specified account. The minted amount is adjusted by the conversion rate.
     * @param account The address for whom the deposit is made.
     * @param amount The amount of the underlying token to deposit.
     * @return Returns true upon success.
     */
    function depositFor(address account, uint256 amount) public override returns (bool) {
        require(amount > 0, "HODLToken: zero amount");
        require(nftCheckSuccess(), "HODLToken: you do not have NFT");
        SafeERC20.safeTransferFrom(underlying, _msgSender(), address(this), amount);
        uint256 adjustAmount = amount.mul(10**conversionRate);
        _mint(account, adjustAmount);
        return true;
    }

    /**
     * @notice Allows a user to withdraw a specified amount of the underlying token by burning an equivalent amount of HODL tokens.
     * @dev The sender cannot be the zero address.
     * @param amount The amount of the underlying token to withdraw.
     * @return Returns true upon success.
     */
    function withdraw(uint256 amount) external returns (bool) {
        require(msg.sender != address(0), "HODLToken: zero address");
        require(amount > 0, "HODLToken: zero amount");
        withdrawTo(msg.sender, amount);
        return true;
    }

    /**
     * @notice Burns a specified amount of HODL tokens from the sender's account and transfers the underlying tokens to a specified account.
     * @dev A withdrawal fee is charged, part of which is burned and part of which is distributed as per the rules in the Distributions contract.
     * @param account The account to receive the underlying tokens.
     * @param amount The amount of HODL tokens to burn.
     * @return Returns true upon success.
     */
    function withdrawTo(address account, uint256 amount) public override returns (bool) {
        uint256 feeAmount = amount.mul(distributions.hodlWithdrawFeeRatio()).div(10000);
        uint256 adjustedAmount = amount.sub(feeAmount).div(10**conversionRate);

        uint256 remainder = amount.sub(feeAmount).sub(adjustedAmount.mul(10**conversionRate));
        uint256 adjustedFee = feeAmount.add(remainder);

        uint256 burnAmount = amount.sub(adjustedFee);
        _burn(_msgSender(), burnAmount);
        for (uint8 i = 0; i < distributions.hodlWithdrawFeeDistributionLength(); i++) {
            (uint8 percentage, address to) = distributions.hodlWithdrawFeeDistribution(i);
            SafeERC20.safeTransferFrom(IERC20(address(this)), _msgSender(), to, adjustedFee.mul(percentage).div(100));
        }
        SafeERC20.safeTransfer(underlying, account, adjustedAmount);
        return true;
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The default value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

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

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overridden;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        unchecked {
            _approve(sender, _msgSender(), currentAllowance - amount);
        }

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(_msgSender(), spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `sender` to `recipient`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

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

        emit Transfer(sender, recipient, amount);

        _afterTokenTransfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

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

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

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

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

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

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

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}

// File: @openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC20.sol";
import "../utils/SafeERC20.sol";

/**
 * @dev Extension of the ERC20 token contract to support token wrapping.
 *
 * Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". This is useful
 * in conjunction with other modules. For example, combining this wrapping mechanism with {ERC20Votes} will allow the
 * wrapping of an existing "basic" ERC20 into a governance token.
 *
 * _Available since v4.2._
 */
abstract contract ERC20Wrapper is ERC20 {
    IERC20 public immutable underlying;

    constructor(IERC20 underlyingToken) {
        underlying = underlyingToken;
    }

    /**
     * @dev Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens.
     */
    function depositFor(address account, uint256 amount) public virtual returns (bool) {
        SafeERC20.safeTransferFrom(underlying, _msgSender(), address(this), amount);
        _mint(account, amount);
        return true;
    }

    /**
     * @dev Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens.
     */
    function withdrawTo(address account, uint256 amount) public virtual returns (bool) {
        _burn(_msgSender(), amount);
        SafeERC20.safeTransfer(underlying, account, amount);
        return true;
    }

    /**
     * @dev Mint wrapped token to cover any underlyingTokens that would have been transfered by mistake. Internal
     * function that can be exposed with access control if desired.
     */
    function _recover(address account) internal virtual returns (uint256) {
        uint256 value = underlying.balanceOf(address(this)) - totalSupply();
        _mint(account, value);
        return value;
    }
}

// File: @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC20.sol";

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

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

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

// File: contracts/token/DOBToken.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Arrays.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

/**
 * @title The DOB Token
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice This is the contract for $DOB, the main token of the DeOrderBook protocol
 * @dev This contract defines the $DOB token, which is the governance token for the DeOrderBook system. It is an ERC20 token with added governance features like voting and delegation.
 */
contract DOB is ERC20Upgradeable {
    using SafeMath for uint256;
    using Arrays for uint256[];
    using Counters for Counters.Counter;

    /**
     * @notice Maximum supply of tokens that can ever exist (1 billion tokens with 18 decimals)
     * @dev Change this value if you want a different maximum supply
     */
    uint256 public MAX_SUPPLY;

    /**
     * @notice Address of the current administrator
     * @dev The admin has elevated permissions to perform certain operations
     */
    address public admin;

    /**
     * @notice Address of the pending administrator
     * @dev The pending admin is a proposed new admin that will become the admin when they accept the role
     */
    address public pendingAdmin;

    /**
     * @notice Mapping of an account's address to its delegate's address
     * @dev This delegate will vote on behalf of the account in governance matters
     */
    mapping(address => address) public delegates;

    /**
     * @notice Struct for marking number of votes from a given block
     * @dev The struct contains two properties: fromBlock (block number when the checkpoint was recorded)
     * and votes (voting power)
     */
    struct Checkpoint {
        uint256 fromBlock;
        uint256 votes;
    }

    /**
     * @notice Mapping to record votes checkpoints for each account
     * @dev This nested mapping records the checkpoints for each account.
     * The outer mapping is keyed by an address, and the inner mapping is keyed by a checkpoint index.
     */
    mapping(address => mapping(uint256 => Checkpoint)) public checkpoints;

    /**
     * @notice Mapping to record the number of checkpoints for each account
     * @dev This mapping keeps track of the count of checkpoints for each address
     */
    mapping(address => uint256) public numCheckpoints;

    /**
     * @notice The EIP-712 typehash for the contract's domain
     * @dev This constant is used for generating and verifying EIP-712 typed signatures
     */
    bytes32 public constant DOMAIN_TYPEHASH =
        keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

    /**
     * @notice The EIP-712 typehash for the delegation struct used by the contract
     * @dev This constant is also used for EIP-712 signatures, specifically for delegation operations
     */
    bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");

    /**
     * @notice Mapping to record states for signing/validating signatures
     * @dev This mapping records a nonce for each address. A nonce is a number that is used only once
     */
    mapping(address => uint) public nonces;

    /**
     * @notice Struct to store snapshots of account balances and the total token supply at various points in time
     * @dev Each snapshot has an ID and a value (the account balance or the total supply)
     */
    struct Snapshots {
        uint256[] ids;
        uint256[] values;
    }

    /**
     * @notice Mapping to record snapshots of account balances
     * @dev This mapping records the snapshots of account balances at various points in time
     */
    mapping(address => Snapshots) private _accountBalanceSnapshots;

    /**
     * @notice Snapshots of the total token supply
     * @dev This variable records the snapshots of total token supply at various points in time
     */
    Snapshots private _totalSupplySnapshots;

    /**
     * @notice Counter for unique IDs to snapshots
     * @dev This counter is used to assign unique IDs to snapshots
     */
    Counters.Counter private _currentSnapshotId;

    /**
     * @notice Triggered when a new snapshot is created.
     * @dev A new snapshot is created with a unique ID.
     * @param id The unique ID of the created snapshot.
     */
    event Snapshot(uint256 id);

    /**
     * @notice Logs when the delegate of a certain address changes.
     * @dev Triggered when an address changes its delegate. This can be used for off-chain tracking of delegate changes.
     * @param delegator The address which had its delegate changed.
     * @param fromDelegate The address of the previous delegate.
     * @param toDelegate The address of the new delegate.
     */
    event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

    /**
     * @notice Triggered when a delegate's vote balance changes.
     * @dev This event is emitted when a delegate's vote balance changes. It's useful for tracking the voting power of delegates.
     * @param delegate The address of the delegate.
     * @param previousBalance The delegate's previous voting balance.
     * @param newBalance The delegate's new voting balance.
     */
    event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);

    /**
     * @notice Triggered when the pending admin is changed.
     * @dev This event is emitted when the contract's pending admin is updated. The new pending admin must call `acceptAdmin` to take over.
     * @param oldPendingAdmin The address of the previous pending admin.
     * @param newPendingAdmin The address of the new pending admin.
     */
    event NewPendingAdmin(address indexed oldPendingAdmin, address indexed newPendingAdmin);

    /**
     * @notice Triggered when the admin of the contract is changed.
     * @dev This event is emitted when a new admin takes over the contract. This is the finalization of an admin change.
     * @param oldAdmin The address of the previous admin.
     * @param newAdmin The address of the new admin.
     */
    event NewAdmin(address indexed oldAdmin, address indexed newAdmin);

    /**
     * @notice Only the admin can call the function it is attached to
     * @dev This function modifier ensures that only the admin can call the function it is attached to
     */
    modifier onlyAdmin() {
        require(msg.sender == admin, "DOB: Caller is not a admin");
        _;
    }

    function __DOB_init(address _admin) external initializer {
        __ERC20_init("DeOrderBook", "DOB");
        admin = _admin;
        MAX_SUPPLY = 1e27;
        _mint(_admin, MAX_SUPPLY);
    }

    /**
     * @notice This function allows the current admin to set a new pending admin.
     * @dev The new pending admin will not have admin rights until they accept the role.
     * @param newPendingAdmin The address of the new pending admin.
     * @return A boolean value indicating whether the operation succeeded.
     */
    function setPendingAdmin(address newPendingAdmin) external onlyAdmin returns (bool) {
        address oldPendingAdmin = pendingAdmin;
        pendingAdmin = newPendingAdmin;

        emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);

        return true;
    }

    /**
     * @notice This function allows the pending admin to accept their role and become the admin.
     * @dev If the caller of this function is not the pending admin or the zero address, it reverts.
     *      On success, it changes the admin to the pending admin and sets the pending admin to the zero address.
     * @return A boolean value indicating whether the operation succeeded.
     */
    function acceptAdmin() external returns (bool) {
        if (msg.sender != pendingAdmin || msg.sender == address(0)) {
            revert("DOB: acceptAdmin: illegal address");
        }
        address oldAdmin = admin;
        address oldPendingAdmin = pendingAdmin;
        admin = pendingAdmin;
        pendingAdmin = address(0);

        emit NewAdmin(oldAdmin, admin);
        emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);

        return true;
    }

    /**
     * @notice This function allows the admin to take a snapshot of token balances and the total supply.
     * @dev This will increment the current snapshot ID and emit a Snapshot event.
     * @return The ID of the new snapshot.
     */
    function snapshot() external virtual onlyAdmin returns (uint256) {
        _currentSnapshotId.increment();

        uint256 currentId = _currentSnapshotId.current();
        emit Snapshot(currentId);
        return currentId;
    }

    /**
     * @notice This function returns the balance of an account at the time of a specific snapshot.
     * @dev If the account balance wasn't snapshotted at the given ID, this function will return the current balance of the account.
     * @param account The address of the account.
     * @param snapshotId The ID of the snapshot.
     * @return The balance of the account at the time of the snapshot.
     */
    function balanceOfAt(address account, uint256 snapshotId) public view virtual returns (uint256) {
        (bool snapshotted, uint256 value) = _valueAt(snapshotId, _accountBalanceSnapshots[account]);

        return snapshotted ? value : balanceOf(account);
    }

    /**
     * @notice This function returns the total supply of tokens at the time of a specific snapshot.
     * @dev If the total supply wasn't snapshotted at the given ID, this function will return the current total supply.
     * @param snapshotId The ID of the snapshot.
     * @return The total supply at the time of the snapshot.
     */
    function totalSupplyAt(uint256 snapshotId) public view virtual returns (uint256) {
        (bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnapshots);

        return snapshotted ? value : totalSupply();
    }

    /**
     * @notice This function gets called before any transfer of tokens. It updates the snapshots accordingly.
     * @dev This function overrides the _beforeTokenTransfer() function of the ERC20 contract.
     * @param from The address of the sender.
     * @param to The address of the recipient.
     * @param amount The amount of tokens being transferred.
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, amount);
        if (from == address(0)) {
            // mint
            _updateAccountSnapshot(to);
            _updateTotalSupplySnapshot();
        } else if (to == address(0)) {
            // burn
            _updateAccountSnapshot(from);
            _updateTotalSupplySnapshot();
        } else {
            // transfer
            _updateAccountSnapshot(from);
            _updateAccountSnapshot(to);
        }
    }

    /**
     * @notice Retrieves the balance or total supply at a particular snapshot.
     * @dev This function finds the value of an account or total supply at a given snapshot ID.
     * - It will revert if the snapshot ID is 0 or if the snapshot ID is greater than the current snapshot ID.
     * - It first finds the upper bound of the snapshot ID in the array of snapshot IDs.
     * - If the index equals the length of the array, the function returns false and 0; otherwise, it returns true and the value of the snapshot at that index.
     * @param snapshotId The ID of the snapshot.
     * @param snapshots The snapshots structure to use for finding the value.
     * @return A boolean indicating whether the snapshot exists, and the value at the snapshot.
     */
    function _valueAt(uint256 snapshotId, Snapshots storage snapshots) private view returns (bool, uint256) {
        require(snapshotId > 0, "ERC20Snapshot: id is 0");
        require(snapshotId <= _currentSnapshotId.current(), "ERC20Snapshot: nonexistent id");

        uint256 index = snapshots.ids.findUpperBound(snapshotId);

        if (index == snapshots.ids.length) {
            return (false, 0);
        } else {
            return (true, snapshots.values[index]);
        }
    }

    /**
     * @notice Updates the account balance snapshot for a given account.
     * @dev Called when a change to an account's balance is made and a snapshot is required.
     * @param account The address of the account for which the snapshot will be updated.
     */
    function _updateAccountSnapshot(address account) private {
        _updateSnapshot(_accountBalanceSnapshots[account], balanceOf(account));
    }

    /**
     * @notice Updates the total supply snapshot.
     * @dev Called when a change to the total supply is made and a snapshot is required.
     */
    function _updateTotalSupplySnapshot() private {
        _updateSnapshot(_totalSupplySnapshots, totalSupply());
    }

    /**
     * @notice Updates a given snapshot with the current value.
     * @dev Pushes a new snapshot id and current value to the snapshots if the last snapshot id is less than the current snapshot id.
     * @param snapshots The snapshots structure to update.
     * @param currentValue The current value to record in the snapshot.
     */
    function _updateSnapshot(Snapshots storage snapshots, uint256 currentValue) private {
        uint256 currentId = _currentSnapshotId.current();
        if (_lastSnapshotId(snapshots.ids) < currentId) {
            snapshots.ids.push(currentId);
            snapshots.values.push(currentValue);
        }
    }

    /**
     * @notice Gets the last snapshot id from an array of ids.
     * @dev If the array is empty, the function returns 0.
     * @param ids An array of snapshot ids.
     * @return The last snapshot id.
     */
    function _lastSnapshotId(uint256[] storage ids) private view returns (uint256) {
        if (ids.length == 0) {
            return 0;
        } else {
            return ids[ids.length - 1];
        }
    }

    /**
     * @notice Delegates voting rights to another address.
     * @dev Assigns voting rights of the msg.sender to the `delegatee`.
     * @param delegatee The address that will receive the voter's voting rights.
     */
    function delegate(address delegatee) external {
        return _delegate(msg.sender, delegatee);
    }

    /**
     * @notice Delegates voting rights to another address using a signature.
     * @dev Assigns voting rights to the `delegatee` using an EIP-712 signature.
     * @param delegatee The address that will receive the voter's voting rights.
     * @param nonce The nonce used to prevent replay attacks.
     * @param expiry The time when the signature expires.
     * @param v The recovery byte of the signature.
     * @param r Half of the ECDSA signature pair.
     * @param s Half of the ECDSA signature pair.
     */
    function delegateBySig(
        address delegatee,
        uint256 nonce,
        uint256 expiry,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        bytes32 domainSeparator = keccak256(
            abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), getChainId(), address(this))
        );
        bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry));
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
        address signatory = ecrecover(digest, v, r, s);
        require(signatory != address(0), "DOB: delegateBySig: invalid signature");
        require(nonce == nonces[signatory]++, "DOB: delegateBySig: invalid nonce");
        require(block.timestamp <= expiry, "DOB: delegateBySig: signature expired");
        return _delegate(signatory, delegatee);
    }

    /**
     * @notice Gets the current votes of an account.
     * @dev Returns the number of votes the account currently has.
     * @param account The account to retrieve the votes from.
     * @return The number of votes the account has.
     */
    function getCurrentVotes(address account) external view returns (uint256) {
        uint256 nCheckpoints = numCheckpoints[account];
        return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
    }

    /**
     * @notice Retrieves the prior number of votes for an account as of a block number.
     * @dev Retrieves the checkpoint for an account at a given block number and returns the number of votes the account had at that time.
     * @param account The account to retrieve the votes from.
     * @param blockNumber The block number to retrieve the votes at.
     * @return The number of votes the account had as of the given block.
     */
    function getPriorVotes(address account, uint256 blockNumber) public view returns (uint256) {
        require(blockNumber < block.number, "DOB: getPriorVotes: not yet determined");

        uint256 nCheckpoints = numCheckpoints[account];
        if (nCheckpoints == 0) {
            return 0;
        }

        if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
            return checkpoints[account][nCheckpoints - 1].votes;
        }

        if (checkpoints[account][0].fromBlock > blockNumber) {
            return 0;
        }

        uint256 lower = 0;
        uint256 upper = nCheckpoints - 1;
        while (upper > lower) {
            uint256 center = upper - (upper - lower) / 2;
            Checkpoint memory cp = checkpoints[account][center];
            if (cp.fromBlock == blockNumber) {
                return cp.votes;
            } else if (cp.fromBlock < blockNumber) {
                lower = center;
            } else {
                upper = center - 1;
            }
        }
        return checkpoints[account][lower].votes;
    }

    /**
     * @notice Internal function to delegate an account's voting rights to another address.
     * @dev Delegates voting rights from one account to another. This updates the delegation mapping and vote counts.
     * @param delegator The account that is delegating their votes.
     * @param delegatee The account that is receiving the votes.
     */
    function _delegate(address delegator, address delegatee) internal {
        address currentDelegate = delegates[delegator];
        uint256 delegatorBalance = balanceOf(delegator);
        delegates[delegator] = delegatee;

        emit DelegateChanged(delegator, currentDelegate, delegatee);

        _moveDelegates(currentDelegate, delegatee, delegatorBalance);
    }

    /**
     * @notice Overrides the _transfer function in the ERC20 contract to also move delegated votes.
     * @dev Transfers tokens from one account to another and moves delegated votes if the delegatees are different.
     * @param sender The account sending the tokens.
     * @param recipient The account receiving the tokens.
     * @param amount The amount of tokens to send.
     */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual override {
        super._transfer(sender, recipient, amount);
        _moveDelegates(delegates[sender], delegates[recipient], amount);
    }

    /**
     * @notice Moves votes from one delegate to another.
     * @dev If the source address and the destination address are different and the amount of tokens to transfer is greater than zero,
     *      subtract the amount of tokens from the source address's votes and add the amount of tokens to the destination address's votes.
     * @param srcRep Source address of the delegate.
     * @param dstRep Destination address of the delegate.
     * @param amount The amount of tokens to move.
     */
    function _moveDelegates(
        address srcRep,
        address dstRep,
        uint256 amount
    ) internal {
        if (srcRep != dstRep && amount > 0) {
            if (srcRep != address(0)) {
                uint256 srcRepNum = numCheckpoints[srcRep];
                uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
                uint256 srcRepNew = srcRepOld.sub(amount);
                _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
            }

            if (dstRep != address(0)) {
                uint256 dstRepNum = numCheckpoints[dstRep];
                uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
                uint256 dstRepNew = dstRepOld.add(amount);
                _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
            }
        }
    }

    /**
     * @notice Records a new checkpoint for an account's votes.
     * @dev Writes a new checkpoint for the delegatee's number of votes.
     * @param delegatee The account for which to record a new vote checkpoint.
     * @param nCheckpoints The current number of checkpoints for this account.
     * @param oldVotes The previous number of votes for this account.
     * @param newVotes The new number of votes for this account.
     */
    function _writeCheckpoint(
        address delegatee,
        uint256 nCheckpoints,
        uint256 oldVotes,
        uint256 newVotes
    ) internal {
        uint256 blockNumber = safe32(block.number, "DOB:: _writeCheckpoint: block number exceeds 32 bits");

        if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {
            checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
        } else {
            checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);
            numCheckpoints[delegatee] = nCheckpoints + 1;
        }

        emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
    }

    /**
     * @notice Makes sure a given number can fit into 32 bits.
     * @dev Throws an error message if the given number is greater than 2^32 - 1.
     * @param n The number to check.
     * @param errorMessage The error message to use if the check fails.
     * @return The original number, if it's less than 2^32.
     */
    function safe32(uint256 n, string memory errorMessage) internal pure returns (uint256) {
        require(n < 2**32, errorMessage);
        return uint256(n);
    }

    /**
     * @notice Returns the current chain ID.
     * @dev Fetches and returns the current chain ID using assembly code.
     * @return The current chain ID.
     */
    function getChainId() internal view returns (uint256) {
        uint256 chainId;
        assembly {
            chainId := chainid()
        }
        return chainId;
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./math/Math.sol";

/**
 * @dev Collection of functions related to array types.
 */
library Arrays {
    /**
     * @dev Searches a sorted `array` and returns the first index that contains
     * a value greater or equal to `element`. If no such index exists (i.e. all
     * values in the array are strictly less than `element`), the array length is
     * returned. Time complexity O(log n).
     *
     * `array` is expected to be sorted in ascending order, and to contain no
     * repeated elements.
     */
    function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
        if (array.length == 0) {
            return 0;
        }

        uint256 low = 0;
        uint256 high = array.length;

        while (low < high) {
            uint256 mid = Math.average(low, high);

            // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
            // because Math.average rounds down (it does integer division with truncation).
            if (array[mid] > element) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
        if (low > 0 && array[low - 1] == element) {
            return low - 1;
        } else {
            return low;
        }
    }
}

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title Counters
 * @author Matt Condon (@shrugs)
 * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
 * of elements in a mapping, issuing ERC721 ids, or counting request ids.
 *
 * Include with `using Counters for Counters.Counter;`
 */
library Counters {
    struct Counter {
        // This variable should never be directly accessed by users of the library: interactions must be restricted to
        // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
        // this feature: see https://github.com/ethereum/solidity/issues/4637
        uint256 _value; // default: 0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        unchecked {
            counter._value += 1;
        }
    }

    function decrement(Counter storage counter) internal {
        uint256 value = counter._value;
        require(value > 0, "Counter: decrement overflow");
        unchecked {
            counter._value = value - 1;
        }
    }

    function reset(Counter storage counter) internal {
        counter._value = 0;
    }
}

// File: @openzeppelin/contracts/utils/math/Math.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a / b + (a % b == 0 ? 0 : 1);
    }
}

// File: contracts/mock/ERC721Mock.sol



// SPDX-License-Identifier: MIT

// contracts/GameItem.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract ERC721Mock is ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() public ERC721("HodlTicket", "HTT") {}

    function mintNFT(address player, string memory tokenURI) public onlyOwner returns (uint256) {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(player, newItemId);
        _setTokenURI(newItemId, tokenURI);

        return newItemId;
    }
}

// File: @openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC721.sol";

/**
 * @dev ERC721 token with storage based token URI management.
 */
abstract contract ERC721URIStorage is ERC721 {
    using Strings for uint256;

    // Optional mapping for token URIs
    mapping(uint256 => string) private _tokenURIs;

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = _baseURI();

        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI));
        }

        return super.tokenURI(tokenId);
    }

    /**
     * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual override {
        super._burn(tokenId);

        if (bytes(_tokenURIs[tokenId]).length != 0) {
            delete _tokenURIs[tokenId];
        }
    }
}

// File: contracts/option/ContractGenerator.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.4;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import {OptionToken} from "../token/OptionToken.sol";
import {CloneFactory} from "./CloneFactory.sol";
import {Option} from "../option/Option.sol";

/**
 * @title ContractGenerator
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @dev A factory contract that is responsible for generating and cloning various contracts related to options.
 *      These contracts include the OptionToken contracts and the Option contracts.
 */
contract ContractGenerator is CloneFactory, Ownable {
    using SafeMath for uint256;

    /**
     * @notice The address of the optionFactory contract.
     * @dev This address is used for access control in the ContractGenerator contract.
     *      Any functions with the 'onlyFactory' modifier can only be called by this address.
     */
    address public optionFactory;

    /**
     * @dev A private constant that sets the multiplier for calculations in the contract.
     */
    uint256 private constant MULTIPLIER = 10**18;

    /**
     * @dev A private variable used to track if the contract is initiated.
     */
    bool private initiated = false;

    /**
     * @notice Function modifier to restrict access to functions.
     * @dev This modifier only allows the function to be called by the address stored in optionFactory.
     */
    modifier onlyFactory() {
        require(msg.sender == optionFactory, "ContractGenerator: caller is not the optionFactory");
        _;
    }

    /**
     * @notice Returns the code hash of the OptionToken contract creation code.
     * @dev Function to get the keccak256 hash of the bytecode of the OptionToken contract.
     * @return The code hash of the OptionToken contract creation code.
     */
    function optionTokenCodeHash() external pure returns (bytes32) {
        return keccak256(type(OptionToken).creationCode);
    }

    /**
     * @notice Initializes the ContractGenerator contract.
     * @dev Sets the address of the OptionFactory during deployment.
     * @param _factory The address of the optionFactory contract.
     */
    constructor(address _factory) {
        require(_factory != address(0), "ContractGenerator: zero address");
        optionFactory = _factory;
    }

    /**
     * @notice Creates a pair of OptionToken contracts for a given option.
     * @dev This function uses the create2 assembly function to create two new OptionToken contracts.
     * @param _optionId The ID of the option.
     * @param _optionAddress The address of the option contract.
     * @return bullet The address of the bullet OptionToken contract.
     * @return sniper The address of the sniper OptionToken contract.
     */
    function createToken(uint256 _optionId, address _optionAddress)
        external
        onlyFactory
        returns (address bullet, address sniper)
    {
        bytes32 bulletSalt = keccak256(abi.encodePacked(_optionId, _optionAddress, "bullet"));
        bytes32 sniperSalt = keccak256(abi.encodePacked(_optionId, _optionAddress, "sniper"));
        bytes memory bulletBytecode = type(OptionToken).creationCode;
        bytes memory sniperBytecode = type(OptionToken).creationCode;

        assembly {
            bullet := create2(0, add(bulletBytecode, 32), mload(bulletBytecode), bulletSalt)
        }
        assembly {
            sniper := create2(0, add(sniperBytecode, 32), mload(sniperBytecode), sniperSalt)
        }
        OptionToken(bullet).initialize(_optionAddress);
        OptionToken(sniper).initialize(_optionAddress);
    }

    /**
     * @notice Clones an existing pair of OptionToken contracts.
     * @dev This function uses the CloneFactory contract to clone two existing OptionToken contracts.
     * @param _optionAddress The address of the option contract.
     * @param _bulletSource The address of the source bullet OptionToken contract.
     * @param _sniperSource The address of the source sniper OptionToken contract.
     * @return bullet The address of the cloned bullet OptionToken contract.
     * @return sniper The address of the cloned sniper OptionToken contract.
     */
    function cloneToken(
        address _optionAddress,
        address _bulletSource,
        address _sniperSource
    ) external onlyFactory returns (address bullet, address sniper) {
        bullet = createClone(_bulletSource);
        sniper = createClone(_sniperSource);
        OptionToken(bullet).initialize(_optionAddress);
        OptionToken(sniper).initialize(_optionAddress);
    }

    /**
     * @notice Clones an existing option contract.
     * @dev This function uses the CloneFactory contract to clone an existing Option contract.
     * @param _targetAddress The address of the target option contract to be cloned.
     * @param _optionFactory The address of the option factory contract.
     * @return option The address of the cloned option contract.
     */
    function cloneOptionPool(address _targetAddress, address _optionFactory) external onlyFactory returns (address option) {
        option = createClone(_targetAddress);
        Option(option).clone_initialize(_optionFactory);
    }

    /**
     * @notice Creates a new option contract.
     * @dev This function uses the create2 assembly function to create a new Option contract.
     * @param _strikePrice The strike price of the option.
     * @param _exerciseTimestamp The exercise timestamp of the option.
     * @param _optionType The type of the option.
     * @param _optionFactory The address of the option factory contract.
     * @return option The address of the newly created option contract.
     */
    function createOptionContract(
        uint256 _strikePrice,
        uint256 _exerciseTimestamp,
        uint8 _optionType,
        address _optionFactory
    ) external onlyFactory returns (address option) {
        bytes32 optionSalt = keccak256(abi.encodePacked(_strikePrice, _exerciseTimestamp, _optionType));

        bytes memory optionBytecode = abi.encodePacked(type(Option).creationCode, abi.encode(_optionFactory));
        assembly {
            option := create2(0, add(optionBytecode, 32), mload(optionBytecode), optionSalt)
        }
    }
}

// File: contracts/token/OptionToken.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
 * @title A contract for creating and managing Option Tokens within the DeOrderBook system.
 * @author DeOrderBook
 * @custom:license Copyright (c) DeOrderBook, 2023 — All Rights Reserved
 * @notice These tokens represent whole options in the system. Only a designated controller can mint or burn tokens.
 * @dev This contract extends the ERC20 contract with additional functions for minting and burning tokens, and includes checks to ensure that only the designated controller can perform these actions. It also allows for updating the token symbol post deployment.
 */
contract OptionToken is ERC20 {
    /**
     * @notice The ID of the associated option.
     * @dev Publicly accessible variable storing the ID for this specific option.
     */
    uint256 public optionID;

    /**
     * @notice The address of the entity that controls minting and burning.
     * @dev Publicly accessible variable storing the address of the controller.
     */
    address public controller;

    /**
     * @notice A boolean to track if the contract has been initialized.
     * @dev Private variable to prevent multiple initializations.
     */
    bool private initiated = false;

    /**
     * @notice The name of the option token.
     * @dev Private variable to store the name, which can be updated via the `activeInit` function.
     */
    string private _name;

    /**
     * @notice The symbol of the option token.
     * @dev Private variable to store the symbol, which can be updated via the `updateSymbol` function.
     */
    string private _symbol;

    /**
     * @notice Throws if called by any account other than the controller.
     * @dev Modifier that checks if the caller is the controller.
     */
    modifier onlyController() {
        require(msg.sender == controller, "Option Token: caller is not the controller");
        _;
    }

    /**
     * @notice Throws if the contract is not yet initialized.
     * @dev Modifier that checks if the contract is initiated.
     */
    modifier onlyInitiated() {
        require(initiated, "Option Token: contract is not the initiated");
        _;
    }

    /**
     * @notice Contract constructor.
     * @dev Calls ERC20 constructor with initial token name and symbol.
     */
    constructor() ERC20("Option token", "OptionToken") {}

    /**
     * @notice Returns the name of the token.
     * @dev Overrides the ERC20 name function.
     * @return The name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @notice Returns the symbol of the token.
     * @dev Overrides the ERC20 symbol function.
     * @return The symbol of the token.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @notice Initializes the contract, setting the controller address.
     * @dev Can only be called once, when controller is not set.
     * @param _controller The address of the controller.
     */
    function initialize(address _controller) external {
        require(controller == address(0), "Option Token:controller initiated");
        require(_controller != address(0), "Option Token: zero address");
        controller = _controller;
    }

    /**
     * @notice Activates the contract initialization, setting option ID, name and symbol.
     * @dev Can only be called by the controller and when contract is not initiated.
     * @param _optionID The ID of the associated option.
     * @param _new_name The new name of the token.
     * @param _new_symbol The new symbol of the token.
     */
    function activeInit(
        uint256 _optionID,
        string memory _new_name,
        string memory _new_symbol
    ) external onlyController {
        require(!initiated, "Option Token: initiated");
        _name = _new_name;
        _symbol = _new_symbol;
        optionID = _optionID;
        initiated = true;
    }

    /**
     * @notice Updates the symbol of the token.
     * @dev Can only be called by the controller.
     * @param _new_symbol The new symbol of the token.
     */
    function updateSymbol(string memory _new_symbol) external onlyController {
        _symbol = _new_symbol;
    }

    /**
     * @notice Mints a specified amount of tokens for a given account.
     * @dev Can only be called by the controller and when contract is initiated.
     * @param _account The address of the account to mint tokens for.
     * @param _amount The amount of tokens to be minted.
     */
    function mintFor(address _account, uint256 _amount) external onlyController onlyInitiated {
        _mint(_account, _amount);
    }

    /**
     * @notice Burns a specified amount of tokens from the sender's account.
     * @dev Can only be called by the controller and when contract is initiated.
     * @param amount The amount of tokens to be burned.
     */
    function burn(uint256 amount) external onlyController onlyInitiated {
        _burn(_msgSender(), amount);
    }

    /**
     * @notice Burns a specified amount of tokens from a given account.
     * @dev Can only be called by the controller and when contract is initiated.
     * @param account The address of the account to burn tokens from.
     * @param amount The amount of tokens to be burned.
     */
    function burnFrom(address account, uint256 amount) external onlyController onlyInitiated {
        _burn(account, amount);
    }
}

// File: contracts/option/CloneFactory.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/*
The MIT License (MIT)
Copyright (c) 2018 Murray Software, LLC.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/**
 * @title CloneFactory
 * @author Murray Software, LLC.
 * @notice A factory contract for deploying minimal proxy clones of a target contract.
 * @dev This contract uses assembly to deploy minimal proxy clones that share the same logic but have separate storage.
 * The clones are cheaper to deploy than full contracts and can be used to save gas when creating many instances of the same contract.
 */
contract CloneFactory {
    /**
     * @notice Creates a minimal proxy clone of the target contract
     * @dev Uses inline assembly to create a minimal proxy clone of a target contract
     * @param target The address of the target contract to clone
     * @return result The address of the created clone
     */
    function createClone(address target) internal returns (address result) {
        bytes20 targetBytes = bytes20(target);
        assembly {
            let clone := mload(0x40)
            mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(clone, 0x14), targetBytes)
            mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            result := create(0, clone, 0x37)
        }
    }

    /**
     * @notice Checks if a given address is a clone of the target contract
     * @dev Uses inline assembly to compare the bytecode of the query address to the expected clone bytecode
     * @param target The address of the target contract to compare against
     * @param query The address to check if it is a clone of the target
     * @return result True if the query address is a clone of the target, otherwise False
     */
    function isClone(address target, address query) internal view returns (bool result) {
        bytes20 targetBytes = bytes20(target);
        assembly {
            let clone := mload(0x40)
            mstore(clone, 0x363d3d373d3d3d363d7300000000000000000000000000000000000000000000)
            mstore(add(clone, 0xa), targetBytes)
            mstore(add(clone, 0x1e), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)

            let other := add(clone, 0x40)
            extcodecopy(query, other, 0, 0x2d)
            result := and(eq(mload(clone), mload(other)), eq(mload(add(clone, 0xd)), mload(add(other, 0xd))))
        }
    }
}

// File: contracts/mock/ERC20MockDecimals8.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.4;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract ERC20MockDecimals8 is ERC20 {
    constructor(
        string memory name,
        string memory symbol,
        uint256 supply
    ) public ERC20(name, symbol) {
        _mint(msg.sender, supply);
    }

    function decimals() public view virtual override returns (uint8) {
        return 8;
    }
}

// File: contracts/mock/ERC20MockDecimals6.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.4;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract ERC20MockDecimals6 is ERC20 {
    constructor(
        string memory name,
        string memory symbol,
        uint256 supply
    ) public ERC20(name, symbol) {
        _mint(msg.sender, supply);
    }

    function decimals() public view virtual override returns (uint8) {
        return 6;
    }
}

// File: contracts/mock/ERC20Mock.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.4;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract ERC20Mock is ERC20 {
    constructor(
        string memory name,
        string memory symbol,
        uint256 supply
    ) public ERC20(name, symbol) {
        _mint(msg.sender, supply);
    }
}

// File: contracts/governance/TimeLock.sol



// SPDX-License-Identifier: Apache-2.0
// Compound Timelock https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol.
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract Timelock {
    using SafeMath for uint;

    event NewAdmin(address indexed newAdmin);
    event NewPendingAdmin(address indexed newPendingAdmin);
    event NewDelay(uint indexed newDelay);
    event CancelTransaction(
        bytes32 indexed txHash,
        address indexed target,
        uint value,
        string signature,
        bytes data,
        uint eta
    );
    event ExecuteTransaction(
        bytes32 indexed txHash,
        address indexed target,
        uint value,
        string signature,
        bytes data,
        uint eta
    );
    event QueueTransaction(
        bytes32 indexed txHash,
        address indexed target,
        uint value,
        string signature,
        bytes data,
        uint eta
    );

    uint public constant GRACE_PERIOD = 14 days;
    uint public constant MINIMUM_DELAY = 2 days;
    uint public constant MAXIMUM_DELAY = 30 days;

    address public admin;
    address public pendingAdmin;
    uint public delay;

    mapping(bytes32 => bool) public queuedTransactions;

    constructor(address admin_, uint delay_) {
        require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay.");
        require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");

        admin = admin_;
        delay = delay_;
    }

    receive() external payable {}

    function setDelay(uint delay_) public {
        require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock.");
        require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay.");
        require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
        delay = delay_;

        emit NewDelay(delay);
    }

    function acceptAdmin() public {
        require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin.");
        admin = msg.sender;
        pendingAdmin = address(0);

        emit NewAdmin(admin);
    }

    function setPendingAdmin(address pendingAdmin_) public {
        require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock.");
        pendingAdmin = pendingAdmin_;

        emit NewPendingAdmin(pendingAdmin);
    }

    function queueTransaction(
        address target,
        uint value,
        string memory signature,
        bytes memory data,
        uint eta
    ) public returns (bytes32) {
        require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin.");
        require(
            eta >= getBlockTimestamp().add(delay),
            "Timelock::queueTransaction: Estimated execution block must satisfy delay."
        );

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        queuedTransactions[txHash] = true;

        emit QueueTransaction(txHash, target, value, signature, data, eta);
        return txHash;
    }

    function cancelTransaction(
        address target,
        uint value,
        string memory signature,
        bytes memory data,
        uint eta
    ) public {
        require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin.");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        queuedTransactions[txHash] = false;

        emit CancelTransaction(txHash, target, value, signature, data, eta);
    }

    function executeTransaction(
        address target,
        uint value,
        string memory signature,
        bytes memory data,
        uint eta
    ) public payable returns (bytes memory) {
        require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin.");

        bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
        require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
        require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
        require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale.");

        queuedTransactions[txHash] = false;

        bytes memory callData;

        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        // solium-disable-next-line security/no-call-value
        (bool success, bytes memory returnData) = target.call{value: value}(callData);
        require(success, "Timelock::executeTransaction: Transaction execution reverted.");

        emit ExecuteTransaction(txHash, target, value, signature, data, eta);

        return returnData;
    }

    function getBlockTimestamp() internal view returns (uint) {
        // solium-disable-next-line security/no-block-members
        return block.timestamp;
    }
}