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



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

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() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

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

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(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");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

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



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

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library 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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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

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: contracts/rewards-booster/interfaces/IRewardsBooster.sol



// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.10;

/**
 * @title Asymetrix Protocol V2 IRewardsBooster
 * @author Asymetrix Protocol Inc Team
 * @notice An interface of the RewardsBooster contract.
 */
interface IRewardsBooster {
    /**
     * @notice Initial parameters structure.
     * @param ticket A Ticket contract address.
     * @param stEthOracle An oracle for stETH token.
     * @param asxOracle An oracle for ASX token that returns price of ASX token in WETH.
     * @param nonfungiblePositionManager A NonfungiblePositionManager contract address.
     * @param uniswapWrapper A wrapper contract address that helps to interact with Uniswap V3.
     * @param weth WETH token address.
     * @param asx ASX token address.
     * @param maxBoost A maximum possible boost coefficient. Is used when lock (position) is created by a user.
     * @param minBoostThreshold A threshold (in %) that all of the user's locks (positions) together must overcome for a
     *                          boost to be awarded during a rewards claim on the StakePrizePool contract.
     * @param maxBoostThreshold A threshold (in %) that all of the user's locks (positions) together must achieve to get
     *                          the maximum boost during a rewards claim on the StakePrizePool contract.
     * @param slippageTolerance A slippage tolerance to apply in time of swap of ETH/WETH for ASX.
     */
    struct InitParams {
        address ticket;
        address stEthOracle;
        address asxOracle;
        address nonfungiblePositionManager;
        address uniswapWrapper;
        address weth;
        address asx;
        uint16 maxBoost;
        uint16 minBoostThreshold;
        uint16 maxBoostThreshold;
        uint16 slippageTolerance;
    }

    /**
     * @notice Staking pool structure.
     * @param isFungible Indicates if pool accepts fungible ERC-20 or non fungible ERC-721 tokens.
     * @param stakeToken Token address to stake in the staking pool.
     * @param stakeTokenValuer Valuer for the staked token.
     * @param liquidityPool Liquidity pool where liquidity is stored.
     * @param validator Validator whose main role is to execute different validations in time of stakes and unstakes.
     * @param minLockDuration Minimum look duration in the staking pool.
     * @param maxLockDuration Maximum look duration in the staking pool.
     * @param lockDurationSettingsNumber An amount of settings in the mapping with lock duration settings.
     */
    struct Pool {
        uint256 isFungible;
        address stakeToken;
        address stakeTokenValuer;
        address liquidityPool;
        address validator;
        uint32 minLockDuration;
        uint32 maxLockDuration;
        uint8 lockDurationSettingsNumber;
    }

    /**
     * @notice User structure.
     * @param initializedLocksNumber Number of locks (positions) created and initialized by user.
     */
    struct User {
        uint8 initializedLocksNumber;
    }

    /**
     * @notice Lock (position) structure.
     * @param amountOrId Amount of ERC-20 locked tokens (or ERC-721 NFT locked position ID).
     * @param createdAt Timestamp when the lock (position) was created.
     * @param updatedAt Timestamp when the lock (position) was updated last ime.
     * @param duration Duration of the lock (position).
     * @param maxBoost A maximum possible boost coefficient for user. Is set when lock (position) is created by a user.
     * @param isInitialized Indicates if the lock (position) is initialized or not.
     */
    struct Lock {
        uint256 amountOrId;
        uint32 createdAt;
        uint32 updatedAt;
        uint32 duration;
        uint16 maxBoost;
        bool isInitialized;
    }

    /**
     * @notice LockDurationSettings structure.
     * @param lowerLockDuration Lower lock duration (in seconds).
     * @param upperLockDuration Upper lock duration (in seconds).
     * @param additionalBoost An additional boost that will be applied for locks that are in range of above durations.
     */
    struct LockDurationSettings {
        uint32 lowerLockDuration;
        uint32 upperLockDuration;
        uint16 additionalBoost;
    }

    /**
     * @notice Event emitted when a new lock (position) is created by a user.
     * @param _pid Staking pool ID.
     * @param _user Lock (position) creator address.
     * @param _lid Lock (position) ID.
     * @param _lock Lock (position) structure.
     */
    event LockCreated(uint8 indexed _pid, address indexed _user, uint8 indexed _lid, Lock _lock);

    /**
     * @notice Event emitted when a lock (position) is closed by a user.
     * @param _pid Staking pool ID.
     * @param _user Lock (position) creator address.
     * @param _lid Lock (position) ID.
     */
    event LockClosed(uint8 indexed _pid, address indexed _user, uint8 indexed _lid);

    /**
     * @notice Event emitted when a lock duration was extended.
     * @param _pid Staking pool ID where to extend a lock duration for the position.
     * @param _user Lock (position) creator address.
     * @param _lid An ID of the lock (position) where to extend a lock duration.
     * @param _newDuration A new duration (in seconds) for the lock of the position.
     */
    event LockExtended(uint8 indexed _pid, address indexed _user, uint8 indexed _lid, uint32 _newDuration);

    /**
     * @notice Event emitted when a new staking pool was added by an owner.
     * @param _pid Staking pool ID.
     * @param _pool Staking pool structure.
     */
    event PoolCreated(uint8 indexed _pid, Pool _pool);

    /**
     * @notice Event emitted when accidentally transferred to this contract token (including ETH) was withdrawn
     *         (rescued) by an owner.
     * @param token A token that was withdraw. If token address is equal to zero address - ETH was withdrawn.
     * @param amountOrId An amount of native/ERC-20 tokens or ID of ERC-721 NFT token that was withdraw.
     * @param isFungible Indicates if token that was withdraw is fungible ERC-20 token.
     * @param recipient A recipient of withdrawn tokens.
     */
    event Rescued(address indexed token, uint256 amountOrId, bool indexed isFungible, address indexed recipient);

    /**
     * @notice Calculates a boost coefficient for the array of users.
     * @param _users An array users to calculate boost coefficient for.
     * @return An array with boost coefficients for the users.
     * @return An array with flags that indicate if a user is able to use this coefficient to multiply his rewards.
     */
    function getBoostBatch(address[] calldata _users) external view returns (uint32[] memory, bool[] memory);

    /**
     * @notice Calculates a boost coefficient for a user.
     * @param _user A user to calculate boost coefficient for.
     * @return A boost coefficient for a user.
     * @return A flag that indicates if a user is able to use this coefficient to multiply his rewards.
     */
    function getBoost(address _user) external view returns (uint32, bool);

    /**
     * @notice Returns staking pool info by its ID.
     * @param _pid Staking pool ID.
     * @return Staking pool info.
     */
    function getPoolInfo(uint8 _pid) external view returns (Pool memory);
}

// File: contracts/rewards-booster/interfaces/IValidator.sol



// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.10;

/**
 * @title Asymetrix Protocol V2 IValidator
 * @author Asymetrix Protocol Inc Team
 * @notice An interface that all validators should implement.
 */
interface IValidator {
    /**
     * @notice Validates if stake parameters are valid.
     * @param _pid Staking pool ID.
     * @param _amountOrId Amount of ERC-20 LP tokens or ID of ERC-721 NFT token to validate.
     */
    function validateStake(uint8 _pid, uint256 _amountOrId) external;
}

// File: contracts/rewards-booster/interfaces/IValuer.sol



// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.10;

/**
 * @title Asymetrix Protocol V2 IValuer
 * @author Asymetrix Protocol Inc Team
 * @notice An interface that all valuers should implement.
 */
interface IValuer {
    /**
     * @notice Returns the value of ERC-20 LP tokens (or ERC-721 NFT position) in USD in the liquidity pool.
     * @param _amountOrId An amount of ERC-20 LP tokens (or ERC-721 NFT position ID) to value.
     * @return _value The value of ERC-20 LP tokens (or ERC-721 NFT position) in USD.
     */
    function value(uint256 _amountOrId) external view returns (uint256 _value);

    /**
     * @notice Returns token amounts inside the position in USD.
     * @param _id Id of the Uniswap V3 position.
     * @return _value0 The first token amount in USD.
     * @return _value1 The second token amount in USD.
     */
    function getTokenAmountsInUSD(uint256 _id) external view returns (uint256 _value0, uint256 _value1);
}

// File: contracts/rewards-booster/libraries/RewardsBoosterErrors.sol



// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.10;

/**
 * @title Asymetrix Protocol V2 RewardsBoosterErrors library
 * @author Asymetrix Protocol Inc Team
 * @notice A library with errors for rewards booster contracts.
 */
library RewardsBoosterErrors {
    error WrongLockDurtionSettingsNumber();
    error WrongBalancerPoolTokensNumber();
    error WrongLockDurtionSettings();
    error TooSmallBoostThreshold();
    error NoEmptySlotsInThisPool();
    error InvalidStakeArguments();
    error WrongValidityDuration();
    error TooBigBoostThreshold();
    error TooMuchLocksCreated();
    error StakeTokenWithdraw();
    error LockIsNotFinished();
    error WrongLockDurtion();
    error NotExistingPool();
    error NotExistingLock();
    error LengthsMismatch();
    error NotContract();
    error ZeroAddress();
    error ZeroAmount();
    error StalePrice();
    error WrongTick();
    error WrongTokensRatio();
    error WrongMaxTokenDominance();
    error StubMethod();
}

// File: contracts/rewards-booster/validators/ValidatorUniswapV3.sol



// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.10;

import { INonfungiblePositionManager } from "../../vendor/uniswap-v3/interfaces/INonfungiblePositionManager.sol";
import { IUniswapV3Factory } from "../../vendor/uniswap-v3/interfaces/IUniswapV3Factory.sol";
import { TickMath } from "../../vendor/uniswap-v3/libraries/TickMath.sol";
import { RewardsBoosterErrors } from "../libraries/RewardsBoosterErrors.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IRewardsBooster } from "../interfaces/IRewardsBooster.sol";
import { IValidator } from "../interfaces/IValidator.sol";
import { IValuer } from "../interfaces/IValuer.sol";

/**
 * @title Asymetrix Protocol V2 ValidatorUniswapV3
 * @author Asymetrix Protocol Inc Team
 * @notice A validator that validates staking parameters in time of staking in the Uniswap V3 staking pool on the
 *         RewardsBooster contract.
 */
contract ValidatorUniswapV3 is Ownable, IValidator {
    using Address for address;

    INonfungiblePositionManager private positionManager;
    IUniswapV3Factory private factory;
    IRewardsBooster private rewardsBooster;
    IValuer private valuerUniV3;

    uint256 private maxTokenDominance;

    int24 private minTick;
    int24 private maxTick;

    uint256 public constant ONE_HUNDRED_PERCENT = 10000;

    /**
     * @notice Deploy the ValidatorUniswapV3 contract.
     * @param _positionManager Uniswap V3 NonfungiblePositionManager contract address.
     * @param _factory Uniswap V3 Factory contract address.
     * @param _rewardsBooster RewardsBooster contract address.
     * @param _valuerUniV3 an address of the Uniswap V3 valuer.
     * @param _maxTick max tick allowed for the position.
     * @param _minTick min tick allowed for the position.
     * @param _maxTokenDominance max token dominance.
     */
    constructor(
        address _positionManager,
        address _factory,
        address _rewardsBooster,
        address _valuerUniV3,
        int24 _maxTick,
        int24 _minTick,
        uint256 _maxTokenDominance
    ) {
        onlyContract(_positionManager);
        onlyContract(_factory);
        onlyContract(_rewardsBooster);

        _setMaxTick(_maxTick);
        _setMinTick(_minTick);
        _setValuerUniV3(_valuerUniV3);
        _setMaxTokenDominance(_maxTokenDominance);

        positionManager = INonfungiblePositionManager(_positionManager);
        factory = IUniswapV3Factory(_factory);
        rewardsBooster = IRewardsBooster(_rewardsBooster);
    }

    /**
     * @notice Sets new max token dominance.
     * @param _maxTokenDominance new max token dominance.
     */
    function setMaxTokenDominance(uint256 _maxTokenDominance) external onlyOwner {
        _setMaxTokenDominance(_maxTokenDominance);
    }

    /**
     * @notice Sets new max tick.
     * @param _maxTick new max tick.
     */
    function setMaxTick(int24 _maxTick) external onlyOwner {
        _setMaxTick(_maxTick);
    }

    /**
     * @notice Sets new min tick.
     * @param _minTick new min tick.
     */
    function setMinTick(int24 _minTick) external onlyOwner {
        _setMinTick(_minTick);
    }

    /**
     * @notice Sets a Uniswap V3 valuer address.
     * @param _valuerUniV3 new address of the Uniswap V3 valuer.
     */
    function setValuerUniV3(address _valuerUniV3) external onlyOwner {
        _setValuerUniV3(_valuerUniV3);
    }

    /// @inheritdoc IValidator
    function validateStake(uint8 _pid, uint256 _tokenId) external view {
        INonfungiblePositionManager.Position memory _position = positionManager.positions(_tokenId);

        address _liquidityPool = factory.getPool(_position.token0, _position.token1, _position.fee);
        IRewardsBooster.Pool memory _stakingPool = rewardsBooster.getPoolInfo(_pid);

        if (_liquidityPool != _stakingPool.liquidityPool) revert RewardsBoosterErrors.InvalidStakeArguments();
        if (_position.tickLower != minTick || _position.tickUpper != maxTick) revert RewardsBoosterErrors.WrongTick();

        (uint256 _value0, uint256 _value1) = valuerUniV3.getTokenAmountsInUSD(_tokenId);

        uint256 _maxTokenDominance = maxTokenDominance;
        uint256 _totalInUSD = _value0 + _value1;
        uint256 _tokenDominance = (_value0 * ONE_HUNDRED_PERCENT) / _totalInUSD;

        if (
            _tokenDominance < (ONE_HUNDRED_PERCENT - _maxTokenDominance) ||
            _tokenDominance / _totalInUSD > _maxTokenDominance
        ) revert RewardsBoosterErrors.WrongTokensRatio();
    }

    /**
     * @notice Returns maxTick.
     * @return maxTick.
     */
    function getMaxTick() external view returns (int24) {
        return maxTick;
    }

    /**
     * @notice Returns minTick.
     * @return minTick.
     */
    function getMinTick() external view returns (int24) {
        return minTick;
    }

    /**
     * @notice Returns NonfungiblePositionManager contract address.
     * @return NonfungiblePositionManager contract address.
     */
    function getPositionManager() external view returns (INonfungiblePositionManager) {
        return positionManager;
    }

    /**
     * @notice Returns UniswapV3Factory contract address.
     * @return UniswapV3Factory contract address.
     */
    function getFactory() external view returns (IUniswapV3Factory) {
        return factory;
    }

    /**
     * @notice Returns RewardsBooster contract address.
     * @return RewardsBooster contract address.
     */
    function getRewardsBooster() external view returns (IRewardsBooster) {
        return rewardsBooster;
    }

    /**
     * @notice Private function which sets new max token dominance.
     * @param _maxTokenDominance new max token dominance.
     */
    function _setMaxTokenDominance(uint256 _maxTokenDominance) private {
        if (_maxTokenDominance > ONE_HUNDRED_PERCENT) revert RewardsBoosterErrors.WrongMaxTokenDominance();

        maxTokenDominance = _maxTokenDominance;
    }

    /**
     * @notice Private function which sets new max tick.
     * @param _maxTick new max tick.
     */
    function _setMaxTick(int24 _maxTick) private {
        if (_maxTick > TickMath.MAX_TICK || _maxTick < TickMath.MIN_TICK) revert RewardsBoosterErrors.WrongTick();

        maxTick = _maxTick;
    }

    /**
     * @notice Private function which sets new min tick.
     * @param _minTick new min tick.
     */
    function _setMinTick(int24 _minTick) private {
        if (_minTick < TickMath.MIN_TICK || _minTick > TickMath.MAX_TICK) revert RewardsBoosterErrors.WrongTick();

        minTick = _minTick;
    }

    /**
     * @notice Private function which sets a Uniswap V3 valuer address.
     * @param _valuerUniV3 new address of the Uniswap V3 valuer.
     */
    function _setValuerUniV3(address _valuerUniV3) private {
        onlyContract(_valuerUniV3);

        valuerUniV3 = IValuer(_valuerUniV3);
    }

    /**
     * @notice Checks if an address is a contract.
     * @param _contract An address to check.
     */
    function onlyContract(address _contract) private view {
        if (!_contract.isContract()) revert RewardsBoosterErrors.NotContract();
    }
}

// File: contracts/vendor/uniswap-v3/interfaces/INonfungiblePositionManager.sol



// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity >=0.5.0;

/// @title Non-fungible token for positions
/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred
/// and authorized.
interface INonfungiblePositionManager {
    // details about the uniswap position
    struct Position {
        // the nonce for permits
        uint96 nonce;
        // the address that is approved for spending this token
        address operator;
        // the first token in the pool
        address token0;
        // the second token in the pool
        address token1;
        // the pool's fee
        uint24 fee;
        // the tick range of the position
        int24 tickLower;
        int24 tickUpper;
        // the liquidity of the position
        uint128 liquidity;
        // the fee growth of the aggregate position as of the last action on the individual position
        uint256 feeGrowthInside0LastX128;
        uint256 feeGrowthInside1LastX128;
        // how many uncollected tokens are owed to the position, as of the last computation
        uint128 tokensOwed0;
        uint128 tokensOwed1;
    }

    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }

    /// @notice Returns the position information associated with a given token ID.
    /// @dev Throws if the token ID is not valid.
    /// @param tokenId The ID of the token that represents the position.
    /// @return The position information associated with a given token ID.
    function positions(uint256 tokenId) external view returns (Position memory);

    /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
    /// @param params tokenId The ID of the NFT for which tokens are being collected,
    /// recipient The account that should receive the tokens,
    /// amount0Max The maximum amount of token0 to collect,
    /// amount1Max The maximum amount of token1 to collect
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
}

// File: contracts/vendor/uniswap-v3/interfaces/IUniswapV3Factory.sol



// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity >=0.5.0;

/// @title The interface for the Uniswap V3 Factory
/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees
interface IUniswapV3Factory {
    /// @notice Emitted when the owner of the factory is changed
    /// @param oldOwner The owner before the owner was changed
    /// @param newOwner The owner after the owner was changed
    event OwnerChanged(address indexed oldOwner, address indexed newOwner);

    /// @notice Emitted when a pool is created
    /// @param token0 The first token of the pool by address sort order
    /// @param token1 The second token of the pool by address sort order
    /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
    /// @param tickSpacing The minimum number of ticks between initialized ticks
    /// @param pool The address of the created pool
    event PoolCreated(
        address indexed token0,
        address indexed token1,
        uint24 indexed fee,
        int24 tickSpacing,
        address pool
    );

    /// @notice Emitted when a new fee amount is enabled for pool creation via the factory
    /// @param fee The enabled fee, denominated in hundredths of a bip
    /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee
    event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing);

    /// @notice Returns the current owner of the factory
    /// @dev Can be changed by the current owner via setOwner
    /// @return The address of the factory owner
    function owner() external view returns (address);

    /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled
    /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context
    /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee
    /// @return The tick spacing
    function feeAmountTickSpacing(uint24 fee) external view returns (int24);

    /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
    /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
    /// @param tokenA The contract address of either token0 or token1
    /// @param tokenB The contract address of the other token
    /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
    /// @return pool The pool address
    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);

    /// @notice Creates a pool for the given two tokens and fee
    /// @param tokenA One of the two tokens in the desired pool
    /// @param tokenB The other of the two tokens in the desired pool
    /// @param fee The desired fee for the pool
    /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved
    /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments
    /// are invalid.
    /// @return pool The address of the newly created pool
    function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool);

    /// @notice Updates the owner of the factory
    /// @dev Must be called by the current owner
    /// @param _owner The new owner of the factory
    function setOwner(address _owner) external;

    /// @notice Enables a fee amount with the given tickSpacing
    /// @dev Fee amounts may never be removed once enabled
    /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6)
    /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount
    function enableFeeAmount(uint24 fee, int24 tickSpacing) external;
}

// File: contracts/vendor/uniswap-v3/libraries/TickMath.sol



// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity 0.8.10;

/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    error T();
    error R();

    /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
    int24 internal constant MAX_TICK = -MIN_TICK;

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
    /// at the given tick
    function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        unchecked {
            uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
            if (absTick > uint256(int256(MAX_TICK))) revert T();

            uint256 ratio = absTick & 0x1 != 0
                ? 0xfffcb933bd6fad37aa2d162d1a594001
                : 0x100000000000000000000000000000000;
            if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
            if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
            if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
            if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
            if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
            if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
            if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
            if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
            if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
            if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
            if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
            if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
            if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
            if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
            if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
            if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
            if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
            if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
            if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

            if (tick > 0) ratio = type(uint256).max / ratio;

            // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
            // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
            // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
            sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
        }
    }

    /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
    function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
        unchecked {
            // second inequality must be < because the price can never reach the price at the max tick
            if (!(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO)) revert R();
            uint256 ratio = uint256(sqrtPriceX96) << 32;

            uint256 r = ratio;
            uint256 msb = 0;

            assembly {
                let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(5, gt(r, 0xFFFFFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(4, gt(r, 0xFFFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(3, gt(r, 0xFF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(2, gt(r, 0xF))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := shl(1, gt(r, 0x3))
                msb := or(msb, f)
                r := shr(f, r)
            }
            assembly {
                let f := gt(r, 0x1)
                msb := or(msb, f)
            }

            if (msb >= 128) r = ratio >> (msb - 127);
            else r = ratio << (127 - msb);

            int256 log_2 = (int256(msb) - 128) << 64;

            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(63, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(62, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(61, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(60, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(59, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(58, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(57, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(56, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(55, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(54, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(53, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(52, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(51, f))
                r := shr(f, r)
            }
            assembly {
                r := shr(127, mul(r, r))
                let f := shr(128, r)
                log_2 := or(log_2, shl(50, f))
            }

            int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number

            int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
            int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

            tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
        }
    }
}