ETH Price: $4,282.79 (-0.66%)

Token

STEER_UNIV3_VAULT_11 (STEERUV11)

Overview

Max Total Supply

0.000006484788845142 STEERUV11

Holders

122

Market

Price

$0.00 @ 0.000000 ETH

Onchain Market Cap

-

Circulating Supply Market Cap

-

Other Info

Token Contract (WITH 18 Decimals)

Balance
0 STEERUV11

Value
$0.00
0x9ab1433913c1014e2166b3c7f039d9528b1fc540
Loading...
Loading
Loading...
Loading
Loading...
Loading

Click here to update the token information / general information
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.

Contract Source Code Verified (Exact Match)

Contract Name:
BeaconProxy

Compiler Version
v0.8.12+commit.f00d7308

Optimization Enabled:
Yes with 10 runs

Other Settings:
default evmVersion
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/beacon/BeaconProxy.sol)

pragma solidity ^0.8.0;

import "./IBeacon.sol";
import "../Proxy.sol";
import "../ERC1967/ERC1967Upgrade.sol";

/**
 * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}.
 *
 * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't
 * conflict with the storage layout of the implementation behind the proxy.
 *
 * _Available since v3.4._
 */
contract BeaconProxy is Proxy, ERC1967Upgrade {
    /**
     * @dev Initializes the proxy with `beacon`.
     *
     * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
     * will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity
     * constructor.
     *
     * Requirements:
     *
     * - `beacon` must be a contract with the interface {IBeacon}.
     */
    constructor(address beacon, bytes memory data) payable {
        _upgradeBeaconToAndCall(beacon, data, false);
    }

    /**
     * @dev Returns the current beacon address.
     */
    function _beacon() internal view virtual returns (address) {
        return _getBeacon();
    }

    /**
     * @dev Returns the current implementation address of the associated beacon.
     */
    function _implementation() internal view virtual override returns (address) {
        return IBeacon(_getBeacon()).implementation();
    }

    /**
     * @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}.
     *
     * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon.
     *
     * Requirements:
     *
     * - `beacon` must be a contract.
     * - The implementation returned by `beacon` must be a contract.
     */
    function _setBeacon(address beacon, bytes memory data) internal virtual {
        _upgradeBeaconToAndCall(beacon, data, false);
    }
}

File 2 of 177 : IAlgebraPool.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4;

import './pool/IAlgebraPoolImmutables.sol';
import './pool/IAlgebraPoolState.sol';
import './pool/IAlgebraPoolActions.sol';
import './pool/IAlgebraPoolPermissionedActions.sol';
import './pool/IAlgebraPoolEvents.sol';
import './pool/IAlgebraPoolErrors.sol';

/// @title The interface for a Algebra Pool
/// @dev The pool interface is broken up into many smaller pieces.
/// This interface includes custom error definitions and cannot be used in older versions of Solidity.
/// For older versions of Solidity use #IAlgebraPoolLegacy
/// Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPool is
  IAlgebraPoolImmutables,
  IAlgebraPoolState,
  IAlgebraPoolActions,
  IAlgebraPoolPermissionedActions,
  IAlgebraPoolEvents,
  IAlgebraPoolErrors
{
  // used only for combining interfaces
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by permissioned addresses
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolPermissionedActions {
  /// @notice Set the community's % share of the fees. Only factory owner or POOLS_ADMINISTRATOR_ROLE role
  /// @param newCommunityFee The new community fee percent in thousandths (1e-3)
  function setCommunityFee(uint16 newCommunityFee) external;

  /// @notice Set the new tick spacing values. Only factory owner or POOLS_ADMINISTRATOR_ROLE role
  /// @param newTickSpacing The new tick spacing value
  function setTickSpacing(int24 newTickSpacing) external;

  /// @notice Set the new plugin address. Only factory owner or POOLS_ADMINISTRATOR_ROLE role
  /// @param newPluginAddress The new plugin address
  function setPlugin(address newPluginAddress) external;

  /// @notice Set new plugin config
  /// @param newConfig In the new configuration of the plugin,
  /// each bit of which is responsible for a particular hook.
  /// Only factory owner or POOLS_ADMINISTRATOR_ROLE role
  function setPluginConfig(uint8 newConfig) external;

  /// @notice Set new pool fee. Can be called by owner if dynamic fee is disabled.
  /// Called by the plugin if dynamic fee is enabled
  /// @param newFee The new fee value
  function setFee(uint16 newFee) external;
}

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

pragma solidity ^0.8.0;

import "../IERC721Upgradeable.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721MetadataUpgradeable is IERC721Upgradeable {
    /**
     * @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);
}

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

pragma solidity ^0.8.0;

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

/**
 * @dev ERC721 token with storage based token URI management.
 */
abstract contract ERC721URIStorageUpgradeable is Initializable, ERC721Upgradeable {
    function __ERC721URIStorage_init() internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __ERC721URIStorage_init_unchained();
    }

    function __ERC721URIStorage_init_unchained() internal onlyInitializing {
    }
    using StringsUpgradeable 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];
        }
    }
    uint256[49] private __gap;
}

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

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);
}

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

import "./KeeperRegistry.sol";

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "./interfaces/IRewardOrchestrator.sol";
import "./interfaces/IRunnerRewarder.sol";

contract RewardOrchestrator is
    IRewardOrchestrator,
    Initializable,
    UUPSUpgradeable,
    OwnableUpgradeable
{
    uint256 public constant actionThresholdPercent = 51;

    KeeperRegistry public keeperRegistry;
    IRunnerRewarder public rewardTree;

    // First index of hash mapping which is not yet occupied by a hash. This index is where the next proposed hash will be stored to be voted upon.
    uint256 public firstEmptyHashIndex;

    // Mapping hashIndex => hash
    mapping(uint256 => bytes32) public hashChangeRequests;

    // Mapping of hash votes
    mapping(uint256 => uint256) public voteBitmaps;

    // The index of the hash which was approved most recently; i.e. the current hash. No hash older (i.e. proposed earlier) than the current hash may be accepted.
    uint256 public lastExecutedHashId;

    modifier onlyKeeper() {
        // This function reverts if msg.sender is not a keeper
        keeperRegistry.checkLicense(_msgSender());
        _;
    }

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    function initialize(
        address _keeperRegistry,
        address _RunnerRewarder
    ) public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
        rewardTree = IRunnerRewarder(_RunnerRewarder);
        keeperRegistry = KeeperRegistry(_keeperRegistry);
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}

    /**
     * @dev vote (if you are a keeper) on a given action proposal
     * @param _hashId is the hash of the action to be voted on
     * @param vote is the vote to be cast. false: reject, true: approve. false only has an effect if the keeper previously voted true. It resets their vote to false.
     */
    function voteOnHashChangeRequest(uint256 _hashId, bool vote) public {
        // Get voter keeper license, use to construct bitmap
        uint256 license = keeperRegistry.checkLicense(msg.sender);
        uint256 bitmap = 1 << license;
        if (vote) {
            // Add vote to bitmap through AND
            voteBitmaps[_hashId] |= bitmap;
        } else {
            // Remove vote from bitmap through XOR
            voteBitmaps[_hashId] ^= bitmap;
        }
    }

    /**
     * @dev Returns true if an action with given `actionId` is approved by all existing members of the group.
     * It’s up to the contract creators to decide if this method should look at majority votes (based on ownership)
     * or if it should ask consent of all the users irrespective of their ownerships.
     */
    function actionApprovalStatus(uint256 _hash) public view returns (bool) {
        uint256 maxLicenseId = keeperRegistry.maxNumKeepers() + 1;
        uint256 yesVotes;
        uint256 voteDifference;
        uint256 voteBitmap = voteBitmaps[_hash];
        for (uint256 i = 1; i != maxLicenseId; ++i) {
            voteDifference = 1 << i;

            if (voteBitmap & voteDifference == voteDifference) {
                ++yesVotes;
            }
        }

        uint256 numKeepers = keeperRegistry.currentNumKeepers();
        // If there happen to be no keepers, div by zero error will happen here, preventing actions from being executed.
        if ((yesVotes * 100) / numKeepers >= actionThresholdPercent) {
            return true;
        } else return (false);
    }

    /**
     * @dev Creates a proposal to change the reward merkle tree hash to a new hash.
     * @param _hash The new proposed merkle root hash
     */
    function createHashChangeRequest(
        bytes32 _hash
    ) external onlyKeeper returns (uint256) {
        // Cache current hashId
        uint256 hashId = firstEmptyHashIndex;

        // Store proposed hash in hashChangeRequests
        hashChangeRequests[hashId] = _hash;

        // Vote for the hash change request
        voteOnHashChangeRequest(hashId, true);

        // Increment the first empty hash index
        firstEmptyHashIndex = hashId + 1;

        // Return hashId of the new hash change request
        return hashId;
    }

    /**
     * @dev Use this function to execute a hash change request/propsal.
     * @param _hashId is the ID of the hash change request.
     * @return newState the state of the hash change request.
     */
    function executeHashChangeRequest(
        uint256 _hashId
    ) external onlyKeeper returns (State newState) {
        //Require that we're not reverting to a previous hash. Technically the 0th hash can be executed multiple times
        // (if another hasn't been approved since) which isn't really a security concern--all it does is emit a useless event.
        require(
            _hashId > lastExecutedHashId || lastExecutedHashId == 0,
            "HashId should be greater than lastExecutedHashId"
        );

        bool actionApproved = actionApprovalStatus(_hashId);
        if (actionApproved) {
            // If hash is approved
            // This hash is now the most recent hash, so older hashes are rendered invalid
            lastExecutedHashId = _hashId;

            // Set reward merkle tree to new hash
            bytes32 newHash = hashChangeRequests[_hashId];
            emit ExecutedHashChangeRequest(_hashId, newHash);
            rewardTree.changeMerkleRootHash(newHash);
            return State.COMPLETED;
        } else revert("Votes lacking; state still pending");
    }
}

File 8 of 177 : IAlgebraPoolEvents.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Events emitted by a pool
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolEvents {
  /**
   * @notice Emitted exactly once by a pool when #initialize is first called on the pool
   * @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
   * @param price The initial sqrt price of the pool, as a Q64.96
   * @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
   */
  event Initialize(uint160 price, int24 tick);

  /**
   * @notice Emitted when liquidity is minted for a given position
   * @param sender The address that minted the liquidity
   * @param owner The owner of the position and recipient of any minted liquidity
   * @param bottomTick The lower tick of the position
   * @param topTick The upper tick of the position
   * @param liquidityAmount The amount of liquidity minted to the position range
   * @param amount0 How much token0 was required for the minted liquidity
   * @param amount1 How much token1 was required for the minted liquidity
   */
  event Mint(
    address sender,
    address indexed owner,
    int24 indexed bottomTick,
    int24 indexed topTick,
    uint128 liquidityAmount,
    uint256 amount0,
    uint256 amount1
  );

  /**
   * @notice Emitted when fees are collected by the owner of a position
   * @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
   * @param owner The owner of the position for which fees are collected
   * @param recipient The address that received fees
   * @param bottomTick The lower tick of the position
   * @param topTick The upper tick of the position
   * @param amount0 The amount of token0 fees collected
   * @param amount1 The amount of token1 fees collected
   */
  event Collect(address indexed owner, address recipient, int24 indexed bottomTick, int24 indexed topTick, uint128 amount0, uint128 amount1);

  /**
   * @notice Emitted when a position's liquidity is removed
   * @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
   * @param owner The owner of the position for which liquidity is removed
   * @param bottomTick The lower tick of the position
   * @param topTick The upper tick of the position
   * @param liquidityAmount The amount of liquidity to remove
   * @param amount0 The amount of token0 withdrawn
   * @param amount1 The amount of token1 withdrawn
   */
  event Burn(address indexed owner, int24 indexed bottomTick, int24 indexed topTick, uint128 liquidityAmount, uint256 amount0, uint256 amount1);

  /**
   * @notice Emitted by the pool for any swaps between token0 and token1
   * @param sender The address that initiated the swap call, and that received the callback
   * @param recipient The address that received the output of the swap
   * @param amount0 The delta of the token0 balance of the pool
   * @param amount1 The delta of the token1 balance of the pool
   * @param price The sqrt(price) of the pool after the swap, as a Q64.96
   * @param liquidity The liquidity of the pool after the swap
   * @param tick The log base 1.0001 of price of the pool after the swap
   */
  event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick);

  /**
   * @notice Emitted by the pool for any flashes of token0/token1
   * @param sender The address that initiated the swap call, and that received the callback
   * @param recipient The address that received the tokens from flash
   * @param amount0 The amount of token0 that was flashed
   * @param amount1 The amount of token1 that was flashed
   * @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
   * @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
   */
  event Flash(address indexed sender, address indexed recipient, uint256 amount0, uint256 amount1, uint256 paid0, uint256 paid1);

  /**
   * @notice Emitted when the community fee is changed by the pool
   * @param communityFee0New The updated value of the token0 community fee percent
   * @param communityFee1New The updated value of the token1 community fee percent
   */
  event CommunityFee(uint8 communityFee0New, uint8 communityFee1New);

  /**
   * @notice Emitted when the tick spacing changes
   * @param newTickSpacing The updated value of the new tick spacing
   */
  event TickSpacing(int24 newTickSpacing);

  /**
   * @notice Emitted when new activeIncentive is set
   * @param virtualPoolAddress The address of a virtual pool associated with the current active incentive
   */
  event Incentive(address indexed virtualPoolAddress);

  /**
   * @notice Emitted when the fee changes
   * @param fee The value of the token fee
   */
  event Fee(uint16 fee);

  /**
   * @notice Emitted when the LiquidityCooldown changes
   * @param liquidityCooldown The value of locktime for added liquidity
   */
  event LiquidityCooldown(uint32 liquidityCooldown);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/extensions/GovernorSettings.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Extension of {Governor} for settings updatable through governance.
 *
 * _Available since v4.4._
 */
abstract contract GovernorSettingsUpgradeable is Initializable, GovernorUpgradeable {
    uint256 private _votingDelay;
    uint256 private _votingPeriod;
    uint256 private _proposalThreshold;

    event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);
    event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);
    event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold);

    /**
     * @dev Initialize the governance parameters.
     */
    function __GovernorSettings_init(
        uint256 initialVotingDelay,
        uint256 initialVotingPeriod,
        uint256 initialProposalThreshold
    ) internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __IGovernor_init_unchained();
        __GovernorSettings_init_unchained(initialVotingDelay, initialVotingPeriod, initialProposalThreshold);
    }

    function __GovernorSettings_init_unchained(
        uint256 initialVotingDelay,
        uint256 initialVotingPeriod,
        uint256 initialProposalThreshold
    ) internal onlyInitializing {
        _setVotingDelay(initialVotingDelay);
        _setVotingPeriod(initialVotingPeriod);
        _setProposalThreshold(initialProposalThreshold);
    }

    /**
     * @dev See {IGovernor-votingDelay}.
     */
    function votingDelay() public view virtual override returns (uint256) {
        return _votingDelay;
    }

    /**
     * @dev See {IGovernor-votingPeriod}.
     */
    function votingPeriod() public view virtual override returns (uint256) {
        return _votingPeriod;
    }

    /**
     * @dev See {Governor-proposalThreshold}.
     */
    function proposalThreshold() public view virtual override returns (uint256) {
        return _proposalThreshold;
    }

    /**
     * @dev Update the voting delay. This operation can only be performed through a governance proposal.
     *
     * Emits a {VotingDelaySet} event.
     */
    function setVotingDelay(uint256 newVotingDelay) public virtual onlyGovernance {
        _setVotingDelay(newVotingDelay);
    }

    /**
     * @dev Update the voting period. This operation can only be performed through a governance proposal.
     *
     * Emits a {VotingPeriodSet} event.
     */
    function setVotingPeriod(uint256 newVotingPeriod) public virtual onlyGovernance {
        _setVotingPeriod(newVotingPeriod);
    }

    /**
     * @dev Update the proposal threshold. This operation can only be performed through a governance proposal.
     *
     * Emits a {ProposalThresholdSet} event.
     */
    function setProposalThreshold(uint256 newProposalThreshold) public virtual onlyGovernance {
        _setProposalThreshold(newProposalThreshold);
    }

    /**
     * @dev Internal setter for the voting delay.
     *
     * Emits a {VotingDelaySet} event.
     */
    function _setVotingDelay(uint256 newVotingDelay) internal virtual {
        emit VotingDelaySet(_votingDelay, newVotingDelay);
        _votingDelay = newVotingDelay;
    }

    /**
     * @dev Internal setter for the voting period.
     *
     * Emits a {VotingPeriodSet} event.
     */
    function _setVotingPeriod(uint256 newVotingPeriod) internal virtual {
        // voting period must be at least one block long
        require(newVotingPeriod > 0, "GovernorSettings: voting period too low");
        emit VotingPeriodSet(_votingPeriod, newVotingPeriod);
        _votingPeriod = newVotingPeriod;
    }

    /**
     * @dev Internal setter for the proposal threshold.
     *
     * Emits a {ProposalThresholdSet} event.
     */
    function _setProposalThreshold(uint256 newProposalThreshold) internal virtual {
        emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold);
        _proposalThreshold = newProposalThreshold;
    }
    uint256[47] private __gap;
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that never changes
/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values
interface IUniswapV3PoolImmutables {
    /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface
    /// @return The contract address
    function factory() external view returns (address);

    /// @notice The first of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token0() external view returns (address);

    /// @notice The second of the two tokens of the pool, sorted by address
    /// @return The token contract address
    function token1() external view returns (address);

    /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
    /// @return The fee
    function fee() external view returns (uint24);

    /// @notice The pool tick spacing
    /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive
    /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ...
    /// This value is an int24 to avoid casting even though it is always positive.
    /// @return The tick spacing
    function tickSpacing() external view returns (int24);

    /// @notice The maximum amount of position liquidity that can use any tick in the range
    /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
    /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
    /// @return The max amount of liquidity per tick
    function maxLiquidityPerTick() external view returns (uint128);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/extensions/GovernorVotes.sol)

pragma solidity ^0.8.0;

import "../GovernorUpgradeable.sol";
import "../../token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "../../utils/math/MathUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";

/**
 * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token.
 *
 * _Available since v4.3._
 */
abstract contract GovernorVotesUpgradeable is Initializable, GovernorUpgradeable {
    ERC20VotesUpgradeable public token;

    function __GovernorVotes_init(ERC20VotesUpgradeable tokenAddress) internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __IGovernor_init_unchained();
        __GovernorVotes_init_unchained(tokenAddress);
    }

    function __GovernorVotes_init_unchained(ERC20VotesUpgradeable tokenAddress) internal onlyInitializing {
        token = tokenAddress;
    }

    /**
     * Read the voting weight from the token's built in snapshot mechanism (see {IGovernor-getVotes}).
     */
    function getVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) {
        return token.getPastVotes(account, blockNumber);
    }
    uint256[50] private __gap;
}

File 12 of 177 : IBaseDeposit.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6;

/// @dev Interface for supporting depositing 2 assets via periphery
interface IBaseDeposit {
    ///
    /// @dev Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    /// Uniswap until the next rebalance.
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting `amount0` is less than this
    /// @param amount1Min Revert if resulting `amount1` is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0 Amount of token0 deposited
    /// @return amount1 Amount of token1 deposited
    ///
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        external
        returns (
            uint256 shares,
            uint256 amount0,
            uint256 amount1
        );
}

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

pragma solidity ^0.8.0;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
 */
library StorageSlotUpgradeable {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly {
            r.slot := slot
        }
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.12;

interface ISmartRewardsDistributor {

    struct RewardCampaignParams {
        address owner;
        address liquidityPool;
        address rewardToken;
        uint256 escrowAmount;
        uint256 startBlock;
        uint256 endBlock;
        string campaignParamsIPFSHash;
    }

    struct RewardCampaign {
        address owner;
        address liquidityPool;
        address rewardToken;
        uint256 amount;
        uint256 startBlock;
        uint256 endBlock;
        string campaignParamsIPFSHash;
        uint256 abandonmentDeadlineBlock;
        uint256 cancelBlock;
        uint256 credit;
        bool paused;
        bool halted;
    }

    // Events
    event UserClaim(
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 indexed campaign,
        uint256 totalCampaignClaimed
    );

    event Swept(address indexed token, address to, uint256 amount);

    event RewardTokenChanged(address indexed token, uint256 valid);

    event UserClaimPermissionToggled(address indexed user, address indexed claimee, bool valid);

    event SteerClaimedFees(address indexed token, uint256 amount);

    event RewardCampaignCreated(
        uint256 indexed campaignId,
        address owner,
        uint256 startDistribution,
        uint256 endDistribution,
        address indexed liquidityPool,
        address indexed rewardToken,
        uint256 amount,
        string campaignParamsIPFSHash,
        uint256 abandonDeadline
    );

    event CampaignOwnershipTransfer(
        uint256 indexed campaignId,
        address indexed newOwner
    );

    // Event from orchestratorUpdateRootHash(bytes32, unint) 
    event MerkleRootHashUpdated(
        bytes32 newMerkleRoot, uint blockUpdated
    );

    // Event from setFeeDiscount(address,uint256)
    event FeeDiscountChanged(
        address campaignCreator,
        uint256 discount
    );

    event CampaignPaused(uint indexed campaignId, address sender, bool paused);

    event CampaignCanceled(uint indexed campaignId, bool halted, uint cancelBlock, uint outbound, address owner);

    // Event from setSteerFee(uint256)
    event SteerFeesChanged(uint256 newSteerFee);

    // Event from setAbandonmentBlockTimeframe(uint256)
    event AbandonmentBlockTimeframeChanged(uint256 newBlockTimeframe);

    // Event from setFeeCollectionAddress(address)
    event FeeCollectionAddressChanged(address newFeeCollectionAddress);

    // Functions
    function initialize(
        address _orchestratorRelay,
        uint256 _steerFee,
        address _feeCollectionAddress,
        uint256 _abandonmentBlockTimeframe
    ) external;

    function orchestratorUpdateRootHash(bytes32 newMerkleRoot, uint blockUpdated) external;

    function claim(
        address[] calldata users,
        uint256[] calldata campaignIds,
        uint256[] calldata amounts,
        bytes32[][] calldata proofs
    ) external;

    function createRewardCampaign(
        RewardCampaignParams memory newRewardCampaign
    ) external returns (uint256);

    function setFeeDiscount(
        address campaignCreator,
        uint256 discount
    ) external;

    function setAbandonmentBlockTimeframe(uint256 newBlockTimeframe) external;

    function cancelCampaign(uint256 campaignId, bool halt) external returns (uint256);

    function setMinimumRewardToken(
        address token,
        uint256 minAmount
    ) external;

    function transferCampaignOwnership(
        uint256 campaignId,
        address newOwner
    ) external;

    function setSteerFee(uint256 newSteerFee) external;

    function getAvailableFundsUpToBlock(RewardCampaign memory campaign, uint256 lastBlock) external view returns (uint256);

    function setFeeCollectionAddress(address newFeeCollectionAddress) external;

    function toggleCampaignPause(uint campaignId) external returns (bool);
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma abicoder v2;

import './plugin/IAlgebraPluginFactory.sol';

/// @title The interface for the Algebra Factory
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraFactory {
  /// @notice Emitted when a process of ownership renounce is started
  /// @param timestamp The timestamp of event
  /// @param finishTimestamp The timestamp when ownership renounce will be possible to finish
  event RenounceOwnershipStart(uint256 timestamp, uint256 finishTimestamp);

  /// @notice Emitted when a process of ownership renounce cancelled
  /// @param timestamp The timestamp of event
  event RenounceOwnershipStop(uint256 timestamp);

  /// @notice Emitted when a process of ownership renounce finished
  /// @param timestamp The timestamp of ownership renouncement
  event RenounceOwnershipFinish(uint256 timestamp);

  /// @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 pool The address of the created pool
  event Pool(address indexed token0, address indexed token1, address pool);

  /// @notice Emitted when the default community fee is changed
  /// @param newDefaultCommunityFee The new default community fee value
  event DefaultCommunityFee(uint16 newDefaultCommunityFee);

  /// @notice Emitted when the default tickspacing is changed
  /// @param newDefaultTickspacing The new default tickspacing value
  event DefaultTickspacing(int24 newDefaultTickspacing);

  /// @notice Emitted when the default fee is changed
  /// @param newDefaultFee The new default fee value
  event DefaultFee(uint16 newDefaultFee);

  /// @notice Emitted when the defaultPluginFactory address is changed
  /// @param defaultPluginFactoryAddress The new defaultPluginFactory address
  event DefaultPluginFactory(address defaultPluginFactoryAddress);

  /// @notice role that can change communityFee and tickspacing in pools
  /// @return The hash corresponding to this role
  function POOLS_ADMINISTRATOR_ROLE() external view returns (bytes32);

  /// @notice Returns `true` if `account` has been granted `role` or `account` is owner.
  /// @param role The hash corresponding to the role
  /// @param account The address for which the role is checked
  /// @return bool Whether the address has this role or the owner role or not
  function hasRoleOrOwner(bytes32 role, address account) external view returns (bool);

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

  /// @notice Returns the current poolDeployerAddress
  /// @return The address of the poolDeployer
  function poolDeployer() external view returns (address);

  /// @notice Returns the current communityVaultAddress
  /// @return The address to which community fees are transferred
  function communityVault() external view returns (address);

  /// @notice Returns the default community fee
  /// @return Fee which will be set at the creation of the pool
  function defaultCommunityFee() external view returns (uint16);

  /// @notice Returns the default fee
  /// @return Fee which will be set at the creation of the pool
  function defaultFee() external view returns (uint16);

  /// @notice Returns the default tickspacing
  /// @return Tickspacing which will be set at the creation of the pool
  function defaultTickspacing() external view returns (int24);

  /// @notice Return the current pluginFactory address
  /// @return Algebra plugin factory
  function defaultPluginFactory() external view returns (IAlgebraPluginFactory);

  /// @notice Returns the default communityFee and tickspacing
  /// @return communityFee which will be set at the creation of the pool
  /// @return tickSpacing which will be set at the creation of the pool
  /// @return fee which will be set at the creation of the pool
  function defaultConfigurationForPool() external view returns (uint16 communityFee, int24 tickSpacing, uint16 fee);

  /// @notice Deterministically computes the pool address given the token0 and token1
  /// @dev The method does not check if such a pool has been created
  /// @param token0 first token
  /// @param token1 second token
  /// @return pool The contract address of the Algebra pool
  function computePoolAddress(address token0, address token1) external view returns (address pool);

  /// @notice Returns the pool address for a given pair of tokens, 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
  /// @return pool The pool address
  function poolByPair(address tokenA, address tokenB) external view returns (address pool);

  /// @notice returns keccak256 of AlgebraPool init bytecode.
  /// @dev the hash value changes with any change in the pool bytecode
  /// @return Keccak256 hash of AlgebraPool contract init bytecode
  function POOL_INIT_CODE_HASH() external view returns (bytes32);

  /// @return timestamp The timestamp of the beginning of the renounceOwnership process
  function renounceOwnershipStartTimestamp() external view returns (uint256 timestamp);

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

  /// @dev updates default community fee for new pools
  /// @param newDefaultCommunityFee The new community fee, _must_ be <= MAX_COMMUNITY_FEE
  function setDefaultCommunityFee(uint16 newDefaultCommunityFee) external;

  /// @dev updates default fee for new pools
  /// @param newDefaultFee The new  fee, _must_ be <= MAX_DEFAULT_FEE
  function setDefaultFee(uint16 newDefaultFee) external;

  /// @dev updates default tickspacing for new pools
  /// @param newDefaultTickspacing The new tickspacing, _must_ be <= MAX_TICK_SPACING and >= MIN_TICK_SPACING
  function setDefaultTickspacing(int24 newDefaultTickspacing) external;

  /// @dev updates pluginFactory address
  /// @param newDefaultPluginFactory address of new plugin factory
  function setDefaultPluginFactory(address newDefaultPluginFactory) external;

  /// @notice Starts process of renounceOwnership. After that, a certain period
  /// of time must pass before the ownership renounce can be completed.
  function startRenounceOwnership() external;

  /// @notice Stops process of renounceOwnership and removes timer.
  function stopRenounceOwnership() external;
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract HenjinBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0x42B08e7a9211482d3643a126a7dF1895448d3509);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

File 17 of 177 : IAlgebraPoolErrors.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4;

/// @title Errors emitted by a pool
/// @notice Contains custom errors emitted by the pool
/// @dev Custom errors are separated from the common pool interface for compatibility with older versions of Solidity
interface IAlgebraPoolErrors {
  // ####  pool errors  ####

  /// @notice Emitted by the reentrancy guard
  error locked();

  /// @notice Emitted if arithmetic error occurred
  error arithmeticError();

  /// @notice Emitted if an attempt is made to initialize the pool twice
  error alreadyInitialized();

  /// @notice Emitted if an attempt is made to mint or swap in uninitialized pool
  error notInitialized();

  /// @notice Emitted if 0 is passed as amountRequired to swap function
  error zeroAmountRequired();

  /// @notice Emitted if invalid amount is passed as amountRequired to swap function
  error invalidAmountRequired();

  /// @notice Emitted if the pool received fewer tokens than it should have
  error insufficientInputAmount();

  /// @notice Emitted if there was an attempt to mint zero liquidity
  error zeroLiquidityDesired();
  /// @notice Emitted if actual amount of liquidity is zero (due to insufficient amount of tokens received)
  error zeroLiquidityActual();

  /// @notice Emitted if the pool received fewer tokens{0,1} after flash than it should have
  error flashInsufficientPaid0();
  error flashInsufficientPaid1();

  /// @notice Emitted if limitSqrtPrice param is incorrect
  error invalidLimitSqrtPrice();

  /// @notice Tick must be divisible by tickspacing
  error tickIsNotSpaced();

  /// @notice Emitted if a method is called that is accessible only to the factory owner or dedicated role
  error notAllowed();
  /// @notice Emitted if a method is called that is accessible only to the farming
  error onlyFarming();

  error invalidNewTickSpacing();
  error invalidNewCommunityFee();

  error dynamicFeeActive();
  error dynamicFeeDisabled();

  error pluginIsNotConnected();

  error invalidHookResponse(bytes4 selector);

  // ####  LiquidityMath errors  ####
  /// @notice Emitted if liquidity underflows
  error liquiditySub();
  /// @notice Emitted if liquidity overflows
  error liquidityAdd();

  // ####  TickManagement errors  ####
  error topTickLowerOrEqBottomTick();
  error bottomTickLowerThanMIN();
  error topTickAboveMAX();
  error liquidityOverflow();
  error tickIsNotInitialized();
  error tickInvalidLinks();

  // ####  SafeTransfer errors  ####
  error transferFailed();

  // ####  TickMath errors  ####
  error tickOutOfRange();
  error priceOutOfRange();
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/extensions/GovernorVotesQuorumFraction.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token and a quorum expressed as a
 * fraction of the total supply.
 *
 * _Available since v4.3._
 */
abstract contract GovernorVotesQuorumFractionUpgradeable is Initializable, GovernorVotesUpgradeable {
    uint256 private _quorumNumerator;

    event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator);

    function __GovernorVotesQuorumFraction_init(uint256 quorumNumeratorValue) internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __IGovernor_init_unchained();
        __GovernorVotesQuorumFraction_init_unchained(quorumNumeratorValue);
    }

    function __GovernorVotesQuorumFraction_init_unchained(uint256 quorumNumeratorValue) internal onlyInitializing {
        _updateQuorumNumerator(quorumNumeratorValue);
    }

    function quorumNumerator() public view virtual returns (uint256) {
        return _quorumNumerator;
    }

    function quorumDenominator() public view virtual returns (uint256) {
        return 100;
    }

    function quorum(uint256 blockNumber) public view virtual override returns (uint256) {
        return (token.getPastTotalSupply(blockNumber) * quorumNumerator()) / quorumDenominator();
    }

    function updateQuorumNumerator(uint256 newQuorumNumerator) external virtual onlyGovernance {
        _updateQuorumNumerator(newQuorumNumerator);
    }

    function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual {
        require(
            newQuorumNumerator <= quorumDenominator(),
            "GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator"
        );

        uint256 oldQuorumNumerator = _quorumNumerator;
        _quorumNumerator = newQuorumNumerator;

        emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator);
    }
    uint256[49] private __gap;
}

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

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

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

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

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

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

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

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);
    }
}

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

import "./GlyphBaseLiquidityManager.sol";

contract GlyphMultiPositionLiquidityManager is
    GlyphBaseLiquidityManager
{
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

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

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

/// @title Steer Protocol Governance Token
/// @author Steer Protocol
/// @dev This token is used within the governance contracts
contract SteerToken is
    Initializable,
    ERC20Upgradeable,
    ERC20BurnableUpgradeable,
    PausableUpgradeable,
    OwnableUpgradeable,
    ERC20PermitUpgradeable,
    ERC20VotesUpgradeable,
    UUPSUpgradeable
{
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    function initialize(uint256 initialSupply) public initializer {
        __ERC20_init("Steer Protocol", "STEER");
        __ERC20Burnable_init();
        __Pausable_init();
        __Ownable_init();
        __ERC20Permit_init("Steer Protocol");
        __UUPSUpgradeable_init();

        // Mint intial supply to deployer
        _mint(msg.sender, initialSupply * 10 ** 18);
    }

    /// @dev Pause token transfers in the event of emergency
    /// @dev This will only be used for emergency situations and should not be used for any other reason
    function pause() public onlyOwner {
        _pause();
    }

    /// @dev Unpause token transfers in the event of emergency
    /// @dev This will restore token transfers after an emergency event
    function unpause() public onlyOwner {
        _unpause();
    }

    /// @dev Mints tokens to a given address
    /// @param _to The address to transfer to
    /// @param _amount The amount to transfer
    function mint(address _to, uint256 _amount) public onlyOwner {
        _mint(_to, _amount);
    }

    /// @dev Upgrade proxy support function
    /// @param newImplementation The implementation to upgrade to
    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}

    /// @dev Before transfer hooks
    /// @dev This function is called before a transfer is made
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override whenNotPaused {
        super._beforeTokenTransfer(from, to, amount);
    }

    /// @dev After transfer hooks
    /// @dev This function is called before a transfer is made
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) {
        super._afterTokenTransfer(from, to, amount);
    }

    function _mint(
        address to,
        uint256 amount
    ) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) {
        super._mint(to, amount);
    }

    function _burn(
        address account,
        uint256 amount
    ) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) {
        super._burn(account, amount);
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Callback for IAlgebraPoolActions#mint
/// @notice Any contract that calls IAlgebraPoolActions#mint must implement this interface
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraMintCallback {
  /// @notice Called to `msg.sender` after minting liquidity to a position from IAlgebraPool#mint.
  /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity.
  /// The caller of this method _must_ be checked to be a AlgebraPool deployed by the canonical AlgebraFactory.
  /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity
  /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity
  /// @param data Any data passed through by the caller via the IAlgebraPoolActions#mint call
  function algebraMintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata data) external;
}

File 25 of 177 : IAlgebraPool.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import './pool/IAlgebraPoolImmutables.sol';
import './pool/IAlgebraPoolState.sol';
import './pool/IAlgebraPoolDerivedState.sol';
import './pool/IAlgebraPoolActions.sol';
import './pool/IAlgebraPoolPermissionedActions.sol';
import './pool/IAlgebraPoolEvents.sol';

/**
 * @title The interface for a Algebra Pool
 * @dev The pool interface is broken up into many smaller pieces.
 * Credit to Uniswap Labs under GPL-2.0-or-later license:
 * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
 */
interface IAlgebraPool is
  IAlgebraPoolImmutables,
  IAlgebraPoolState,
  IAlgebraPoolDerivedState,
  IAlgebraPoolActions,
  IAlgebraPoolPermissionedActions,
  IAlgebraPoolEvents
{
  // used only for combining interfaces
}

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

interface IBeaconInterface {
    /// @dev Event emitted when the address that the beacon is pointing to is upgraded.
    /// @return address of the new implementation.
    event Upgraded(address indexed newImplementation);

    function implementation() external view returns (address);

    function upgradeImplementationTo(address newImplementation) external;
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract StellaSwapV4BaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0x90dD87C994959A36d725bB98F9008B0b3C3504A0);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

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

import "./QuickSwapIntegralBaseLiquidityManager.sol";

contract QuickSwapIntegralMultiPositionLiquidityManager is
    QuickSwapIntegralBaseLiquidityManager
{
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title The interface for the Algebra volatility oracle
/// @dev This contract stores timepoints and calculates statistical averages
interface IVolatilityOracle {
  /// @notice Returns data belonging to a certain timepoint
  /// @param index The index of timepoint in the array
  /// @dev There is more convenient function to fetch a timepoint: getTimepoints(). Which requires not an index but seconds
  /// @return initialized Whether the timepoint has been initialized and the values are safe to use
  /// @return blockTimestamp The timestamp of the timepoint
  /// @return tickCumulative The tick multiplied by seconds elapsed for the life of the pool as of the timepoint timestamp
  /// @return volatilityCumulative Cumulative standard deviation for the life of the pool as of the timepoint timestamp
  /// @return tick The tick at blockTimestamp
  /// @return averageTick Time-weighted average tick
  /// @return windowStartIndex Index of closest timepoint >= WINDOW seconds ago
  function timepoints(
    uint256 index
  )
    external
    view
    returns (
      bool initialized,
      uint32 blockTimestamp,
      int56 tickCumulative,
      uint88 volatilityCumulative,
      int24 tick,
      int24 averageTick,
      uint16 windowStartIndex
    );

  /// @notice Returns the index of the last timepoint that was written.
  /// @return index of the last timepoint written
  function timepointIndex() external view returns (uint16);

  /// @notice Returns the timestamp of the last timepoint that was written.
  /// @return timestamp of the last timepoint
  function lastTimepointTimestamp() external view returns (uint32);

  /// @notice Returns information about whether oracle is initialized
  /// @return true if oracle is initialized, otherwise false
  function isInitialized() external view returns (bool);

  /// @dev Reverts if a timepoint at or before the desired timepoint timestamp does not exist.
  /// 0 may be passed as `secondsAgo' to return the current cumulative values.
  /// If called with a timestamp falling between two timepoints, returns the counterfactual accumulator values
  /// at exactly the timestamp between the two timepoints.
  /// @dev `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors
  /// @param secondsAgo The amount of time to look back, in seconds, at which point to return a timepoint
  /// @return tickCumulative The cumulative tick since the pool was first initialized, as of `secondsAgo`
  /// @return volatilityCumulative The cumulative volatility value since the pool was first initialized, as of `secondsAgo`
  function getSingleTimepoint(uint32 secondsAgo) external view returns (int56 tickCumulative, uint88 volatilityCumulative);

  /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos`
  /// @dev Reverts if `secondsAgos` > oldest timepoint
  /// @dev `volatilityCumulative` values for timestamps after the last timepoint _should not_ be compared because they may differ due to interpolation errors
  /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return a timepoint
  /// @return tickCumulatives The cumulative tick since the pool was first initialized, as of each `secondsAgo`
  /// @return volatilityCumulatives The cumulative volatility values since the pool was first initialized, as of each `secondsAgo`
  function getTimepoints(uint32[] memory secondsAgos) external view returns (int56[] memory tickCumulatives, uint88[] memory volatilityCumulatives);

  /// @notice Fills uninitialized timepoints with nonzero value
  /// @dev Can be used to reduce the gas cost of future swaps
  /// @param startIndex The start index, must be not initialized
  /// @param amount of slots to fill, startIndex + amount must be <= type(uint16).max
  function prepayTimepointsStorageSlots(uint16 startIndex, uint16 amount) external;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.12;
import "./interfaces/IWhitelistRegistry.sol";

contract WhitelistRegistry is IWhitelistRegistry {
    /**
     * @dev info, per address, of permissions. 0 = no permissions, 1 = whitelisted. This is only relevant for vaults with whitelists.
     */
    mapping(address => mapping(address => uint256)) public permissions;

    mapping(address => address) public whitelistManagers;

    /**
     * @dev add whitelist permissions for any number of addresses.
     * @param _vaultAddress the vault whose whitelist will be edited
     * @param _addresses the addresses to be added to the whitelist
     */
    function addPermissions(
        address _vaultAddress,
        address[] calldata _addresses
    ) external {
        // Make sure caller is authorized
        require(
            msg.sender == whitelistManagers[_vaultAddress],
            "Only Whitelist Manager"
        );
        mapping(address => uint256) storage _permissions = permissions[
            _vaultAddress
        ];
        // Add permissions
        uint256 addressCount = _addresses.length;
        for (uint256 i; i != addressCount; ++i) {
            _permissions[_addresses[i]] = 1;
        }
        emit PermissionsAdded(msg.sender, _vaultAddress, _addresses);
    }

    /**
     * @dev function meant to be called by contracts (usually in initializer) to register a whitelist manager for that contract
     * @param manager the address of the vault's whitelist manager
     * No access control, since any given contract can only modify their own data here.
     */
    function registerWhitelistManager(address manager) external {
        whitelistManagers[msg.sender] = manager;
        emit ManagerAdded(msg.sender, manager);
    }

    /**
     * @dev add whitelist permissions for any number of addresses.
     * @param _vaultAddress the vault whose whitelist will be edited
     * @param _addresses the addresses to be removed from the whitelist
     */
    function revokePermissions(
        address _vaultAddress,
        address[] calldata _addresses
    ) external {
        // Make sure caller is authorized
        require(
            msg.sender == whitelistManagers[_vaultAddress],
            "Only Whitelist Manager"
        );

        mapping(address => uint256) storage _permissions = permissions[
            _vaultAddress
        ];
        // Add permissions
        uint256 addressCount = _addresses.length;
        for (uint256 i; i != addressCount; ++i) {
            _permissions[_addresses[i]] = 0;
        }
        emit PermissionsRemoved(msg.sender, _vaultAddress, _addresses);
    }
}

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

pragma solidity ^0.8.0;

import "./IERC165Upgradeable.sol";
import "../../proxy/utils/Initializable.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 ERC165Upgradeable is Initializable, IERC165Upgradeable {
    function __ERC165_init() internal onlyInitializing {
        __ERC165_init_unchained();
    }

    function __ERC165_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165Upgradeable).interfaceId;
    }
    uint256[50] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/compatibility/GovernorCompatibilityBravo.sol)

pragma solidity ^0.8.0;

import "../../utils/CountersUpgradeable.sol";
import "../../utils/math/SafeCastUpgradeable.sol";
import "../extensions/IGovernorTimelockUpgradeable.sol";
import "../GovernorUpgradeable.sol";
import "./IGovernorCompatibilityBravoUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";

/**
 * @dev Compatibility layer that implements GovernorBravo compatibility on to of {Governor}.
 *
 * This compatibility layer includes a voting system and requires a {IGovernorTimelock} compatible module to be added
 * through inheritance. It does not include token bindings, not does it include any variable upgrade patterns.
 *
 * NOTE: When using this module, you may need to enable the Solidity optimizer to avoid hitting the contract size limit.
 *
 * _Available since v4.3._
 */
abstract contract GovernorCompatibilityBravoUpgradeable is Initializable, IGovernorTimelockUpgradeable, IGovernorCompatibilityBravoUpgradeable, GovernorUpgradeable {
    function __GovernorCompatibilityBravo_init() internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __IGovernor_init_unchained();
        __IGovernorTimelock_init_unchained();
        __IGovernorCompatibilityBravo_init_unchained();
        __GovernorCompatibilityBravo_init_unchained();
    }

    function __GovernorCompatibilityBravo_init_unchained() internal onlyInitializing {
    }
    using CountersUpgradeable for CountersUpgradeable.Counter;
    using TimersUpgradeable for TimersUpgradeable.BlockNumber;

    enum VoteType {
        Against,
        For,
        Abstain
    }

    struct ProposalDetails {
        address proposer;
        address[] targets;
        uint256[] values;
        string[] signatures;
        bytes[] calldatas;
        uint256 forVotes;
        uint256 againstVotes;
        uint256 abstainVotes;
        mapping(address => Receipt) receipts;
        bytes32 descriptionHash;
    }

    mapping(uint256 => ProposalDetails) private _proposalDetails;

    // solhint-disable-next-line func-name-mixedcase
    function COUNTING_MODE() public pure virtual override returns (string memory) {
        return "support=bravo&quorum=bravo";
    }

    // ============================================== Proposal lifecycle ==============================================
    /**
     * @dev See {IGovernor-propose}.
     */
    function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    ) public virtual override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) {
        _storeProposal(_msgSender(), targets, values, new string[](calldatas.length), calldatas, description);
        return super.propose(targets, values, calldatas, description);
    }

    /**
     * @dev See {IGovernorCompatibilityBravo-propose}.
     */
    function propose(
        address[] memory targets,
        uint256[] memory values,
        string[] memory signatures,
        bytes[] memory calldatas,
        string memory description
    ) public virtual override returns (uint256) {
        _storeProposal(_msgSender(), targets, values, signatures, calldatas, description);
        return propose(targets, values, _encodeCalldata(signatures, calldatas), description);
    }

    /**
     * @dev See {IGovernorCompatibilityBravo-queue}.
     */
    function queue(uint256 proposalId) public virtual override {
        ProposalDetails storage details = _proposalDetails[proposalId];
        queue(
            details.targets,
            details.values,
            _encodeCalldata(details.signatures, details.calldatas),
            details.descriptionHash
        );
    }

    /**
     * @dev See {IGovernorCompatibilityBravo-execute}.
     */
    function execute(uint256 proposalId) public payable virtual override {
        ProposalDetails storage details = _proposalDetails[proposalId];
        execute(
            details.targets,
            details.values,
            _encodeCalldata(details.signatures, details.calldatas),
            details.descriptionHash
        );
    }

    function cancel(uint256 proposalId) public virtual override {
        ProposalDetails storage details = _proposalDetails[proposalId];

        require(
            _msgSender() == details.proposer || getVotes(details.proposer, block.number - 1) < proposalThreshold(),
            "GovernorBravo: proposer above threshold"
        );

        _cancel(
            details.targets,
            details.values,
            _encodeCalldata(details.signatures, details.calldatas),
            details.descriptionHash
        );
    }

    /**
     * @dev Encodes calldatas with optional function signature.
     */
    function _encodeCalldata(string[] memory signatures, bytes[] memory calldatas)
        private
        pure
        returns (bytes[] memory)
    {
        bytes[] memory fullcalldatas = new bytes[](calldatas.length);

        for (uint256 i = 0; i < signatures.length; ++i) {
            fullcalldatas[i] = bytes(signatures[i]).length == 0
                ? calldatas[i]
                : abi.encodeWithSignature(signatures[i], calldatas[i]);
        }

        return fullcalldatas;
    }

    /**
     * @dev Store proposal metadata for later lookup
     */
    function _storeProposal(
        address proposer,
        address[] memory targets,
        uint256[] memory values,
        string[] memory signatures,
        bytes[] memory calldatas,
        string memory description
    ) private {
        bytes32 descriptionHash = keccak256(bytes(description));
        uint256 proposalId = hashProposal(targets, values, _encodeCalldata(signatures, calldatas), descriptionHash);

        ProposalDetails storage details = _proposalDetails[proposalId];
        if (details.descriptionHash == bytes32(0)) {
            details.proposer = proposer;
            details.targets = targets;
            details.values = values;
            details.signatures = signatures;
            details.calldatas = calldatas;
            details.descriptionHash = descriptionHash;
        }
    }

    // ==================================================== Views =====================================================
    /**
     * @dev See {IGovernorCompatibilityBravo-proposals}.
     */
    function proposals(uint256 proposalId)
        public
        view
        virtual
        override
        returns (
            uint256 id,
            address proposer,
            uint256 eta,
            uint256 startBlock,
            uint256 endBlock,
            uint256 forVotes,
            uint256 againstVotes,
            uint256 abstainVotes,
            bool canceled,
            bool executed
        )
    {
        id = proposalId;
        eta = proposalEta(proposalId);
        startBlock = proposalSnapshot(proposalId);
        endBlock = proposalDeadline(proposalId);

        ProposalDetails storage details = _proposalDetails[proposalId];
        proposer = details.proposer;
        forVotes = details.forVotes;
        againstVotes = details.againstVotes;
        abstainVotes = details.abstainVotes;

        ProposalState status = state(proposalId);
        canceled = status == ProposalState.Canceled;
        executed = status == ProposalState.Executed;
    }

    /**
     * @dev See {IGovernorCompatibilityBravo-getActions}.
     */
    function getActions(uint256 proposalId)
        public
        view
        virtual
        override
        returns (
            address[] memory targets,
            uint256[] memory values,
            string[] memory signatures,
            bytes[] memory calldatas
        )
    {
        ProposalDetails storage details = _proposalDetails[proposalId];
        return (details.targets, details.values, details.signatures, details.calldatas);
    }

    /**
     * @dev See {IGovernorCompatibilityBravo-getReceipt}.
     */
    function getReceipt(uint256 proposalId, address voter) public view virtual override returns (Receipt memory) {
        return _proposalDetails[proposalId].receipts[voter];
    }

    /**
     * @dev See {IGovernorCompatibilityBravo-quorumVotes}.
     */
    function quorumVotes() public view virtual override returns (uint256) {
        return quorum(block.number - 1);
    }

    // ==================================================== Voting ====================================================
    /**
     * @dev See {IGovernor-hasVoted}.
     */
    function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) {
        return _proposalDetails[proposalId].receipts[account].hasVoted;
    }

    /**
     * @dev See {Governor-_quorumReached}. In this module, only forVotes count toward the quorum.
     */
    function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) {
        ProposalDetails storage details = _proposalDetails[proposalId];
        return quorum(proposalSnapshot(proposalId)) <= details.forVotes;
    }

    /**
     * @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be scritly over the againstVotes.
     */
    function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) {
        ProposalDetails storage details = _proposalDetails[proposalId];
        return details.forVotes > details.againstVotes;
    }

    /**
     * @dev See {Governor-_countVote}. In this module, the support follows Governor Bravo.
     */
    function _countVote(
        uint256 proposalId,
        address account,
        uint8 support,
        uint256 weight
    ) internal virtual override {
        ProposalDetails storage details = _proposalDetails[proposalId];
        Receipt storage receipt = details.receipts[account];

        require(!receipt.hasVoted, "GovernorCompatibilityBravo: vote already cast");
        receipt.hasVoted = true;
        receipt.support = support;
        receipt.votes = SafeCastUpgradeable.toUint96(weight);

        if (support == uint8(VoteType.Against)) {
            details.againstVotes += weight;
        } else if (support == uint8(VoteType.For)) {
            details.forVotes += weight;
        } else if (support == uint8(VoteType.Abstain)) {
            details.abstainVotes += weight;
        } else {
            revert("GovernorCompatibilityBravo: invalid vote type");
        }
    }
    uint256[49] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/Governor.sol)

pragma solidity ^0.8.0;

import "../utils/cryptography/ECDSAUpgradeable.sol";
import "../utils/cryptography/draft-EIP712Upgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import "../utils/math/SafeCastUpgradeable.sol";
import "../utils/AddressUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/TimersUpgradeable.sol";
import "./IGovernorUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Core of the governance system, designed to be extended though various modules.
 *
 * This contract is abstract and requires several function to be implemented in various modules:
 *
 * - A counting module must implement {quorum}, {_quorumReached}, {_voteSucceeded} and {_countVote}
 * - A voting module must implement {getVotes}
 * - Additionanly, the {votingPeriod} must also be implemented
 *
 * _Available since v4.3._
 */
abstract contract GovernorUpgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, EIP712Upgradeable, IGovernorUpgradeable {
    using SafeCastUpgradeable for uint256;
    using TimersUpgradeable for TimersUpgradeable.BlockNumber;

    bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)");

    struct ProposalCore {
        TimersUpgradeable.BlockNumber voteStart;
        TimersUpgradeable.BlockNumber voteEnd;
        bool executed;
        bool canceled;
    }

    string private _name;

    mapping(uint256 => ProposalCore) private _proposals;

    /**
     * @dev Restrict access to governor executing address. Some module might override the _executor function to make
     * sure this modifier is consistant with the execution model.
     */
    modifier onlyGovernance() {
        require(_msgSender() == _executor(), "Governor: onlyGovernance");
        _;
    }

    /**
     * @dev Sets the value for {name} and {version}
     */
    function __Governor_init(string memory name_) internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __EIP712_init_unchained(name_, version());
        __IGovernor_init_unchained();
        __Governor_init_unchained(name_);
    }

    function __Governor_init_unchained(string memory name_) internal onlyInitializing {
        _name = name_;
    }

    /**
     * @dev Function to receive ETH that will be handled by the governor (disabled if executor is a third party contract)
     */
    receive() external payable virtual {
        require(_executor() == address(this));
    }

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

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

    /**
     * @dev See {IGovernor-version}.
     */
    function version() public view virtual override returns (string memory) {
        return "1";
    }

    /**
     * @dev See {IGovernor-hashProposal}.
     *
     * The proposal id is produced by hashing the RLC encoded `targets` array, the `values` array, the `calldatas` array
     * and the descriptionHash (bytes32 which itself is the keccak256 hash of the description string). This proposal id
     * can be produced from the proposal data which is part of the {ProposalCreated} event. It can even be computed in
     * advance, before the proposal is submitted.
     *
     * Note that the chainId and the governor address are not part of the proposal id computation. Consequently, the
     * same proposal (with same operation and same description) will have the same id if submitted on multiple governors
     * accross multiple networks. This also means that in order to execute the same operation twice (on the same
     * governor) the proposer will have to change the description in order to avoid proposal id conflicts.
     */
    function hashProposal(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public pure virtual override returns (uint256) {
        return uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash)));
    }

    /**
     * @dev See {IGovernor-state}.
     */
    function state(uint256 proposalId) public view virtual override returns (ProposalState) {
        ProposalCore memory proposal = _proposals[proposalId];

        if (proposal.executed) {
            return ProposalState.Executed;
        } else if (proposal.canceled) {
            return ProposalState.Canceled;
        } else if (proposal.voteStart.getDeadline() >= block.number) {
            return ProposalState.Pending;
        } else if (proposal.voteEnd.getDeadline() >= block.number) {
            return ProposalState.Active;
        } else if (proposal.voteEnd.isExpired()) {
            return
                _quorumReached(proposalId) && _voteSucceeded(proposalId)
                    ? ProposalState.Succeeded
                    : ProposalState.Defeated;
        } else {
            revert("Governor: unknown proposal id");
        }
    }

    /**
     * @dev See {IGovernor-proposalSnapshot}.
     */
    function proposalSnapshot(uint256 proposalId) public view virtual override returns (uint256) {
        return _proposals[proposalId].voteStart.getDeadline();
    }

    /**
     * @dev See {IGovernor-proposalDeadline}.
     */
    function proposalDeadline(uint256 proposalId) public view virtual override returns (uint256) {
        return _proposals[proposalId].voteEnd.getDeadline();
    }

    /**
     * @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_.
     */
    function proposalThreshold() public view virtual returns (uint256) {
        return 0;
    }

    /**
     * @dev Amount of votes already cast passes the threshold limit.
     */
    function _quorumReached(uint256 proposalId) internal view virtual returns (bool);

    /**
     * @dev Is the proposal successful or not.
     */
    function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool);

    /**
     * @dev Register a vote with a given support and voting weight.
     *
     * Note: Support is generic and can represent various things depending on the voting system used.
     */
    function _countVote(
        uint256 proposalId,
        address account,
        uint8 support,
        uint256 weight
    ) internal virtual;

    /**
     * @dev See {IGovernor-propose}.
     */
    function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    ) public virtual override returns (uint256) {
        require(
            getVotes(msg.sender, block.number - 1) >= proposalThreshold(),
            "GovernorCompatibilityBravo: proposer votes below proposal threshold"
        );

        uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description)));

        require(targets.length == values.length, "Governor: invalid proposal length");
        require(targets.length == calldatas.length, "Governor: invalid proposal length");
        require(targets.length > 0, "Governor: empty proposal");

        ProposalCore storage proposal = _proposals[proposalId];
        require(proposal.voteStart.isUnset(), "Governor: proposal already exists");

        uint64 snapshot = block.number.toUint64() + votingDelay().toUint64();
        uint64 deadline = snapshot + votingPeriod().toUint64();

        proposal.voteStart.setDeadline(snapshot);
        proposal.voteEnd.setDeadline(deadline);

        emit ProposalCreated(
            proposalId,
            _msgSender(),
            targets,
            values,
            new string[](targets.length),
            calldatas,
            snapshot,
            deadline,
            description
        );

        return proposalId;
    }

    /**
     * @dev See {IGovernor-execute}.
     */
    function execute(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public payable virtual override returns (uint256) {
        uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);

        ProposalState status = state(proposalId);
        require(
            status == ProposalState.Succeeded || status == ProposalState.Queued,
            "Governor: proposal not successful"
        );
        _proposals[proposalId].executed = true;

        emit ProposalExecuted(proposalId);

        _execute(proposalId, targets, values, calldatas, descriptionHash);

        return proposalId;
    }

    /**
     * @dev Internal execution mechanism. Can be overriden to implement different execution mechanism
     */
    function _execute(
        uint256, /* proposalId */
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 /*descriptionHash*/
    ) internal virtual {
        string memory errorMessage = "Governor: call reverted without message";
        for (uint256 i = 0; i < targets.length; ++i) {
            (bool success, bytes memory returndata) = targets[i].call{value: values[i]}(calldatas[i]);
            AddressUpgradeable.verifyCallResult(success, returndata, errorMessage);
        }
    }

    /**
     * @dev Internal cancel mechanism: locks up the proposal timer, preventing it from being re-submitted. Marks it as
     * canceled to allow distinguishing it from executed proposals.
     *
     * Emits a {IGovernor-ProposalCanceled} event.
     */
    function _cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal virtual returns (uint256) {
        uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
        ProposalState status = state(proposalId);

        require(
            status != ProposalState.Canceled && status != ProposalState.Expired && status != ProposalState.Executed,
            "Governor: proposal not active"
        );
        _proposals[proposalId].canceled = true;

        emit ProposalCanceled(proposalId);

        return proposalId;
    }

    /**
     * @dev See {IGovernor-castVote}.
     */
    function castVote(uint256 proposalId, uint8 support) public virtual override returns (uint256) {
        address voter = _msgSender();
        return _castVote(proposalId, voter, support, "");
    }

    /**
     * @dev See {IGovernor-castVoteWithReason}.
     */
    function castVoteWithReason(
        uint256 proposalId,
        uint8 support,
        string calldata reason
    ) public virtual override returns (uint256) {
        address voter = _msgSender();
        return _castVote(proposalId, voter, support, reason);
    }

    /**
     * @dev See {IGovernor-castVoteBySig}.
     */
    function castVoteBySig(
        uint256 proposalId,
        uint8 support,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override returns (uint256) {
        address voter = ECDSAUpgradeable.recover(
            _hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support))),
            v,
            r,
            s
        );
        return _castVote(proposalId, voter, support, "");
    }

    /**
     * @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve
     * voting weight using {IGovernor-getVotes} and call the {_countVote} internal function.
     *
     * Emits a {IGovernor-VoteCast} event.
     */
    function _castVote(
        uint256 proposalId,
        address account,
        uint8 support,
        string memory reason
    ) internal virtual returns (uint256) {
        ProposalCore storage proposal = _proposals[proposalId];
        require(state(proposalId) == ProposalState.Active, "Governor: vote not currently active");

        uint256 weight = getVotes(account, proposal.voteStart.getDeadline());
        _countVote(proposalId, account, support, weight);

        emit VoteCast(account, proposalId, support, weight, reason);

        return weight;
    }

    /**
     * @dev Address through which the governor executes action. Will be overloaded by module that execute actions
     * through another contract such as a timelock.
     */
    function _executor() internal view virtual returns (address) {
        return address(this);
    }
    uint256[48] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/TimelockController.sol)

pragma solidity ^0.8.0;

import "../access/AccessControlUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which acts as a timelocked controller. When set as the
 * owner of an `Ownable` smart contract, it enforces a timelock on all
 * `onlyOwner` maintenance operations. This gives time for users of the
 * controlled contract to exit before a potentially dangerous maintenance
 * operation is applied.
 *
 * By default, this contract is self administered, meaning administration tasks
 * have to go through the timelock process. The proposer (resp executor) role
 * is in charge of proposing (resp executing) operations. A common use case is
 * to position this {TimelockController} as the owner of a smart contract, with
 * a multisig or a DAO as the sole proposer.
 *
 * _Available since v3.3._
 */
contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeable {
    bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE");
    bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
    bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
    uint256 internal constant _DONE_TIMESTAMP = uint256(1);

    mapping(bytes32 => uint256) private _timestamps;
    uint256 private _minDelay;

    /**
     * @dev Emitted when a call is scheduled as part of operation `id`.
     */
    event CallScheduled(
        bytes32 indexed id,
        uint256 indexed index,
        address target,
        uint256 value,
        bytes data,
        bytes32 predecessor,
        uint256 delay
    );

    /**
     * @dev Emitted when a call is performed as part of operation `id`.
     */
    event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data);

    /**
     * @dev Emitted when operation `id` is cancelled.
     */
    event Cancelled(bytes32 indexed id);

    /**
     * @dev Emitted when the minimum delay for future operations is modified.
     */
    event MinDelayChange(uint256 oldDuration, uint256 newDuration);

    /**
     * @dev Initializes the contract with a given `minDelay`.
     */
    function __TimelockController_init(
        uint256 minDelay,
        address[] memory proposers,
        address[] memory executors
    ) internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __AccessControl_init_unchained();
        __TimelockController_init_unchained(minDelay, proposers, executors);
    }

    function __TimelockController_init_unchained(
        uint256 minDelay,
        address[] memory proposers,
        address[] memory executors
    ) internal onlyInitializing {
        _setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);
        _setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);
        _setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);

        // deployer + self administration
        _setupRole(TIMELOCK_ADMIN_ROLE, _msgSender());
        _setupRole(TIMELOCK_ADMIN_ROLE, address(this));

        // register proposers
        for (uint256 i = 0; i < proposers.length; ++i) {
            _setupRole(PROPOSER_ROLE, proposers[i]);
        }

        // register executors
        for (uint256 i = 0; i < executors.length; ++i) {
            _setupRole(EXECUTOR_ROLE, executors[i]);
        }

        _minDelay = minDelay;
        emit MinDelayChange(0, minDelay);
    }

    /**
     * @dev Modifier to make a function callable only by a certain role. In
     * addition to checking the sender's role, `address(0)` 's role is also
     * considered. Granting a role to `address(0)` is equivalent to enabling
     * this role for everyone.
     */
    modifier onlyRoleOrOpenRole(bytes32 role) {
        if (!hasRole(role, address(0))) {
            _checkRole(role, _msgSender());
        }
        _;
    }

    /**
     * @dev Contract might receive/hold ETH as part of the maintenance process.
     */
    receive() external payable {}

    /**
     * @dev Returns whether an id correspond to a registered operation. This
     * includes both Pending, Ready and Done operations.
     */
    function isOperation(bytes32 id) public view virtual returns (bool pending) {
        return getTimestamp(id) > 0;
    }

    /**
     * @dev Returns whether an operation is pending or not.
     */
    function isOperationPending(bytes32 id) public view virtual returns (bool pending) {
        return getTimestamp(id) > _DONE_TIMESTAMP;
    }

    /**
     * @dev Returns whether an operation is ready or not.
     */
    function isOperationReady(bytes32 id) public view virtual returns (bool ready) {
        uint256 timestamp = getTimestamp(id);
        return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
    }

    /**
     * @dev Returns whether an operation is done or not.
     */
    function isOperationDone(bytes32 id) public view virtual returns (bool done) {
        return getTimestamp(id) == _DONE_TIMESTAMP;
    }

    /**
     * @dev Returns the timestamp at with an operation becomes ready (0 for
     * unset operations, 1 for done operations).
     */
    function getTimestamp(bytes32 id) public view virtual returns (uint256 timestamp) {
        return _timestamps[id];
    }

    /**
     * @dev Returns the minimum delay for an operation to become valid.
     *
     * This value can be changed by executing an operation that calls `updateDelay`.
     */
    function getMinDelay() public view virtual returns (uint256 duration) {
        return _minDelay;
    }

    /**
     * @dev Returns the identifier of an operation containing a single
     * transaction.
     */
    function hashOperation(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt
    ) public pure virtual returns (bytes32 hash) {
        return keccak256(abi.encode(target, value, data, predecessor, salt));
    }

    /**
     * @dev Returns the identifier of an operation containing a batch of
     * transactions.
     */
    function hashOperationBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata datas,
        bytes32 predecessor,
        bytes32 salt
    ) public pure virtual returns (bytes32 hash) {
        return keccak256(abi.encode(targets, values, datas, predecessor, salt));
    }

    /**
     * @dev Schedule an operation containing a single transaction.
     *
     * Emits a {CallScheduled} event.
     *
     * Requirements:
     *
     * - the caller must have the 'proposer' role.
     */
    function schedule(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt,
        uint256 delay
    ) public virtual onlyRole(PROPOSER_ROLE) {
        bytes32 id = hashOperation(target, value, data, predecessor, salt);
        _schedule(id, delay);
        emit CallScheduled(id, 0, target, value, data, predecessor, delay);
    }

    /**
     * @dev Schedule an operation containing a batch of transactions.
     *
     * Emits one {CallScheduled} event per transaction in the batch.
     *
     * Requirements:
     *
     * - the caller must have the 'proposer' role.
     */
    function scheduleBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata datas,
        bytes32 predecessor,
        bytes32 salt,
        uint256 delay
    ) public virtual onlyRole(PROPOSER_ROLE) {
        require(targets.length == values.length, "TimelockController: length mismatch");
        require(targets.length == datas.length, "TimelockController: length mismatch");

        bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt);
        _schedule(id, delay);
        for (uint256 i = 0; i < targets.length; ++i) {
            emit CallScheduled(id, i, targets[i], values[i], datas[i], predecessor, delay);
        }
    }

    /**
     * @dev Schedule an operation that is to becomes valid after a given delay.
     */
    function _schedule(bytes32 id, uint256 delay) private {
        require(!isOperation(id), "TimelockController: operation already scheduled");
        require(delay >= getMinDelay(), "TimelockController: insufficient delay");
        _timestamps[id] = block.timestamp + delay;
    }

    /**
     * @dev Cancel an operation.
     *
     * Requirements:
     *
     * - the caller must have the 'proposer' role.
     */
    function cancel(bytes32 id) public virtual onlyRole(PROPOSER_ROLE) {
        require(isOperationPending(id), "TimelockController: operation cannot be cancelled");
        delete _timestamps[id];

        emit Cancelled(id);
    }

    /**
     * @dev Execute an (ready) operation containing a single transaction.
     *
     * Emits a {CallExecuted} event.
     *
     * Requirements:
     *
     * - the caller must have the 'executor' role.
     */
    function execute(
        address target,
        uint256 value,
        bytes calldata data,
        bytes32 predecessor,
        bytes32 salt
    ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
        bytes32 id = hashOperation(target, value, data, predecessor, salt);
        _beforeCall(id, predecessor);
        _call(id, 0, target, value, data);
        _afterCall(id);
    }

    /**
     * @dev Execute an (ready) operation containing a batch of transactions.
     *
     * Emits one {CallExecuted} event per transaction in the batch.
     *
     * Requirements:
     *
     * - the caller must have the 'executor' role.
     */
    function executeBatch(
        address[] calldata targets,
        uint256[] calldata values,
        bytes[] calldata datas,
        bytes32 predecessor,
        bytes32 salt
    ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
        require(targets.length == values.length, "TimelockController: length mismatch");
        require(targets.length == datas.length, "TimelockController: length mismatch");

        bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt);
        _beforeCall(id, predecessor);
        for (uint256 i = 0; i < targets.length; ++i) {
            _call(id, i, targets[i], values[i], datas[i]);
        }
        _afterCall(id);
    }

    /**
     * @dev Checks before execution of an operation's calls.
     */
    function _beforeCall(bytes32 id, bytes32 predecessor) private view {
        require(isOperationReady(id), "TimelockController: operation is not ready");
        require(predecessor == bytes32(0) || isOperationDone(predecessor), "TimelockController: missing dependency");
    }

    /**
     * @dev Checks after execution of an operation's calls.
     */
    function _afterCall(bytes32 id) private {
        require(isOperationReady(id), "TimelockController: operation is not ready");
        _timestamps[id] = _DONE_TIMESTAMP;
    }

    /**
     * @dev Execute an operation's call.
     *
     * Emits a {CallExecuted} event.
     */
    function _call(
        bytes32 id,
        uint256 index,
        address target,
        uint256 value,
        bytes calldata data
    ) private {
        (bool success, ) = target.call{value: value}(data);
        require(success, "TimelockController: underlying transaction reverted");

        emit CallExecuted(id, index, target, value, data);
    }

    /**
     * @dev Changes the minimum timelock duration for future operations.
     *
     * Emits a {MinDelayChange} event.
     *
     * Requirements:
     *
     * - the caller must be the timelock itself. This can only be achieved by scheduling and later executing
     * an operation where the timelock is the target and the data is the ABI-encoded call to this function.
     */
    function updateDelay(uint256 newDelay) external virtual {
        require(msg.sender == address(this), "TimelockController: caller must be timelock");
        emit MinDelayChange(_minDelay, newDelay);
        _minDelay = newDelay;
    }
    uint256[48] private __gap;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.12;
import "./IVaultRegistry.sol";
import "./IStrategyRegistry.sol";
import "./IMultiPositionManager.sol";

interface ISteerPeriphery {
    struct CVSParams {
        address strategyCreator;
        string name;
        string execBundle;
        uint128 maxGasCost;
        uint128 maxGasPerAction;
        bytes params;
        string beaconName;
        address vaultManager;
        string payloadIpfs;
    }

    struct CVDGParams {
        uint256 tokenId;
        bytes params;
        string beaconName;
        address vaultManager;
        string payloadIpfs;
    }

    struct CVSRJParams {
        address strategyCreator;
        string name;
        string execBundle;
        uint128 maxGasCost;
        uint128 maxGasPerAction;
        bytes jobInitParams;
        string beaconName;
        address vaultManager;
        string payloadIpfs;
        bytes[] userProvidedData;
        address[] targetAddresses;
        string jobName;
        string ipfsForJobDetails;
    }

    function vaultsByStrategy(
        uint256 _strategyId
    ) external view returns (IVaultRegistry.VaultData[] memory);

    function strategiesByCreator(
        address _address
    ) external view returns (IStrategyRegistry.RegisteredStrategy[] memory);

    function deposit(
        address _vaultAddress,
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external;

    function vaultDetailsByAddress(
        address _vault
    )
        external
        view
        returns (IMultiPositionManager.VaultDetails memory details);

    function vaultBalancesByAddressWithFees(
        address _vault
    ) external returns (IMultiPositionManager.VaultBalance memory balances);

    function createVaultAndStrategy(
        CVSParams calldata cvsParams
    ) external payable returns (uint256 tokenId, address newVault);

    function createVaultAndDepositGas(
        CVDGParams calldata cvdgParams
    ) external payable returns (address newVault);

    function createVaultStrategyAndRegisterJob(
        CVSRJParams calldata cvsrjParams
    ) external payable returns (uint256 tokenId, address newVault);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Upgrade.sol)

pragma solidity ^0.8.2;

import "../beacon/IBeaconUpgradeable.sol";
import "../../utils/AddressUpgradeable.sol";
import "../../utils/StorageSlotUpgradeable.sol";
import "../utils/Initializable.sol";

/**
 * @dev This abstract contract provides getters and event emitting update functions for
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
 *
 * _Available since v4.1._
 *
 * @custom:oz-upgrades-unsafe-allow delegatecall
 */
abstract contract ERC1967UpgradeUpgradeable is Initializable {
    function __ERC1967Upgrade_init() internal onlyInitializing {
        __ERC1967Upgrade_init_unchained();
    }

    function __ERC1967Upgrade_init_unchained() internal onlyInitializing {
    }
    // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
    bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;

    /**
     * @dev Storage slot with the address of the current implementation.
     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Returns the current implementation address.
     */
    function _getImplementation() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 implementation slot.
     */
    function _setImplementation(address newImplementation) private {
        require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
        StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    }

    /**
     * @dev Perform implementation upgrade
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeTo(address newImplementation) internal {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }

    /**
     * @dev Perform implementation upgrade with additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCall(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        _upgradeTo(newImplementation);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(newImplementation, data);
        }
    }

    /**
     * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCallSecure(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        address oldImplementation = _getImplementation();

        // Initial upgrade and setup call
        _setImplementation(newImplementation);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(newImplementation, data);
        }

        // Perform rollback test if not already in progress
        StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT);
        if (!rollbackTesting.value) {
            // Trigger rollback using upgradeTo from the new implementation
            rollbackTesting.value = true;
            _functionDelegateCall(
                newImplementation,
                abi.encodeWithSignature("upgradeTo(address)", oldImplementation)
            );
            rollbackTesting.value = false;
            // Check rollback was effective
            require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
            // Finally reset to the new implementation and log the upgrade
            _upgradeTo(newImplementation);
        }
    }

    /**
     * @dev Storage slot with the admin of the contract.
     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Returns the current admin.
     */
    function _getAdmin() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 admin slot.
     */
    function _setAdmin(address newAdmin) private {
        require(newAdmin != address(0), "ERC1967: new admin is the zero address");
        StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {AdminChanged} event.
     */
    function _changeAdmin(address newAdmin) internal {
        emit AdminChanged(_getAdmin(), newAdmin);
        _setAdmin(newAdmin);
    }

    /**
     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
     * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
     */
    bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    /**
     * @dev Emitted when the beacon is upgraded.
     */
    event BeaconUpgraded(address indexed beacon);

    /**
     * @dev Returns the current beacon.
     */
    function _getBeacon() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value;
    }

    /**
     * @dev Stores a new beacon in the EIP1967 beacon slot.
     */
    function _setBeacon(address newBeacon) private {
        require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract");
        require(
            AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()),
            "ERC1967: beacon implementation is not a contract"
        );
        StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value = newBeacon;
    }

    /**
     * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
     * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
     *
     * Emits a {BeaconUpgraded} event.
     */
    function _upgradeBeaconToAndCall(
        address newBeacon,
        bytes memory data,
        bool forceCall
    ) internal {
        _setBeacon(newBeacon);
        emit BeaconUpgraded(newBeacon);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data);
        }
    }

    /**
     * @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) private returns (bytes memory) {
        require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed");
    }
    uint256[50] private __gap;
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that can change
/// @notice These methods compose the pool's state, and can change with any frequency including multiple times
/// per transaction
interface IUniswapV3PoolState {
    /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas
    /// when accessed externally.
    /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value
    /// tick The current tick of the pool, i.e. according to the last tick transition that was run.
    /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick
    /// boundary.
    /// observationIndex The index of the last oracle observation that was written,
    /// observationCardinality The current maximum number of observations stored in the pool,
    /// observationCardinalityNext The next maximum number of observations, to be updated when the observation.
    /// feeProtocol The protocol fee for both tokens of the pool.
    /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0
    /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee.
    /// unlocked Whether the pool is currently locked to reentrancy
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );

    /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint256
    function feeGrowthGlobal0X128() external view returns (uint256);

    /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
    /// @dev This value can overflow the uint256
    function feeGrowthGlobal1X128() external view returns (uint256);

    /// @notice The amounts of token0 and token1 that are owed to the protocol
    /// @dev Protocol fees will never exceed uint128 max in either token
    function protocolFees() external view returns (uint128 token0, uint128 token1);

    /// @notice The currently in range liquidity available to the pool
    /// @dev This value has no relationship to the total liquidity across all ticks
    function liquidity() external view returns (uint128);

    /// @notice Look up information about a specific tick in the pool
    /// @param tick The tick to look up
    /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or
    /// tick upper,
    /// liquidityNet how much liquidity changes when the pool price crosses the tick,
    /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0,
    /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1,
    /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick
    /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick,
    /// secondsOutside the seconds spent on the other side of the tick from the current tick,
    /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false.
    /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0.
    /// In addition, these values are only relative and must be used only in comparison to previous snapshots for
    /// a specific position.
    function ticks(int24 tick)
        external
        view
        returns (
            uint128 liquidityGross,
            int128 liquidityNet,
            uint256 feeGrowthOutside0X128,
            uint256 feeGrowthOutside1X128,
            int56 tickCumulativeOutside,
            uint160 secondsPerLiquidityOutsideX128,
            uint32 secondsOutside,
            bool initialized
        );

    /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information
    function tickBitmap(int16 wordPosition) external view returns (uint256);

    /// @notice Returns the information about a position by the position's key
    /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper
    /// @return _liquidity The amount of liquidity in the position,
    /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke,
    /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke,
    /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke,
    /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
    function positions(bytes32 key)
        external
        view
        returns (
            uint128 _liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    /// @notice Returns data about a specific observation index
    /// @param index The element of the observations array to fetch
    /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time
    /// ago, rather than at a specific index in the array.
    /// @return blockTimestamp The timestamp of the observation,
    /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp,
    /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp,
    /// Returns initialized whether the observation has been initialized and the values are safe to use
    function observations(uint256 index)
        external
        view
        returns (
            uint32 blockTimestamp,
            int56 tickCumulative,
            uint160 secondsPerLiquidityCumulativeX128,
            bool initialized
        );
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/**
 * @title Pool state that is not stored
 * @notice Contains view functions to provide information about the pool that is computed rather than stored on the
 * blockchain. The functions here may have variable gas costs.
 * @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
 * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
 */
interface IAlgebraPoolDerivedState {
  /**
   * @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
   * @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
   * the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
   * you must call it with secondsAgos = [3600, 0].
   * @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
   * log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
   * @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
   * @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
   * @return secondsPerLiquidityCumulatives Cumulative seconds per liquidity-in-range value as of each `secondsAgos`
   * from the current block timestamp
   * @return volatilityCumulatives Cumulative standard deviation as of each `secondsAgos`
   * @return volumePerAvgLiquiditys Cumulative swap volume per liquidity as of each `secondsAgos`
   */
  function getTimepoints(uint32[] calldata secondsAgos)
    external
    view
    returns (
      int56[] memory tickCumulatives,
      uint160[] memory secondsPerLiquidityCumulatives,
      uint112[] memory volatilityCumulatives,
      uint256[] memory volumePerAvgLiquiditys
    );

  /**
   * @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
   * @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
   * I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
   * snapshot is taken and the second snapshot is taken.
   * @param bottomTick The lower tick of the range
   * @param topTick The upper tick of the range
   * @return innerTickCumulative The snapshot of the tick accumulator for the range
   * @return innerSecondsSpentPerLiquidity The snapshot of seconds per liquidity for the range
   * @return innerSecondsSpent The snapshot of the number of seconds during which the price was in this range
   */
  function getInnerCumulatives(int24 bottomTick, int24 topTick)
    external
    view
    returns (
      int56 innerTickCumulative,
      uint160 innerSecondsSpentPerLiquidity,
      uint32 innerSecondsSpent
    );
}

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

// Inheritance
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import "../interfaces/IStakingRewards.sol";

/// @title Staking reward contract
/// @author Steer Protocol
/// @dev This contract is used to reward stakers for their staking time.
contract StakingRewards is
    IStakingRewards,
    Initializable,
    OwnableUpgradeable,
    UUPSUpgradeable
{
    using SafeERC20Upgradeable for IERC20Upgradeable;

    // Storage

    // Constants
    uint256 public constant PRECISION = 1e18;
    uint256 public constant SECONDS_IN_YEAR = 31_536_000;
    uint256 public constant RATE_PRECISION = 100_00; //Precision for reward calculaion

    // Mapping of Pool details to pool id
    mapping(uint256 => Pool) public pools;

    // Total no. of pools created
    uint256 public totalPools;

    //Mapping of user details per pool
    mapping(uint256 => mapping(address => UserInfo)) public userInfoPerPool;

    // Mapping of total rewards allocated currently for a pool
    mapping(uint256 => uint256) public totalRewardsPerPool;

    // Mapping that returns the state of pool by passing pool id, true means staking is paused and false means staking is allowed
    mapping(uint256 => bool) public isPaused;

    // Mapping that returns pending rewards for a particular user for a particular pool
    mapping(address => mapping(uint256 => uint256)) public pendingRewards;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    // External Functions

    /// @dev To stake tokens
    /// @param amount The number of tokens to be staked.
    /// @param poolId The id of the pool in which tokens should be staked.
    function stake(uint256 amount, uint256 poolId) external {
        _stake(msg.sender, amount, poolId);
    }

    /// @dev To stake tokens
    /// @param user The address that stake tokens for.
    /// @param amount The number of tokens to be staked.
    /// @param poolId The id of the pool in which tokens should be staked.
    function stakeFor(address user, uint256 amount, uint256 poolId) external {
        _stake(user, amount, poolId);
    }

    /// @dev To unstake staked tokens.
    /// @param poolId The id of pool from which the tokens whould be unstaked.
    function unstake(uint256 poolId) external {
        Pool memory pool = pools[poolId];
        UserInfo storage userInfo = userInfoPerPool[poolId][msg.sender];
        uint256 amount = userInfo.balance;
        require(amount != 0, "0 Stake");
        pools[poolId].totalAmount -= amount;
        if (block.timestamp > pool.end) {
            claimReward(poolId, pool, userInfo);
        } else {
            userInfo.lastRewarded = 0;
            userInfo.rewards = 0;
            userInfo.balance = 0;
        }
        IERC20Upgradeable(pool.stakingToken).safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount, poolId);
    }

    /// @dev To claim the pending rewards
    /// @param poolId The id of the pool from which the pending rewards should be claimed
    function claimPendingRewards(uint256 poolId) external {
        uint256 pending = pendingRewards[msg.sender][poolId];
        pendingRewards[msg.sender][poolId] = 0;
        totalRewardsPerPool[poolId] -= pending;
        IERC20Upgradeable(pools[poolId].rewardToken).safeTransfer(
            msg.sender,
            pending
        );
    }

    // Internal Functions

    function claimReward(
        uint256 poolId,
        Pool memory pool,
        UserInfo storage userInfo
    ) internal {
        updateReward(pool, userInfo);
        uint256 reward = userInfo.rewards;
        userInfo.rewards = 0;
        userInfo.balance = 0;
        userInfo.lastRewarded = 0;
        uint256 totalRewards = totalRewardsPerPool[poolId];
        if (totalRewards >= reward) {
            totalRewardsPerPool[poolId] = totalRewards - reward;
            emit RewardPaid(msg.sender, poolId, reward);
            IERC20Upgradeable(pool.rewardToken).safeTransfer(
                msg.sender,
                reward
            );
        } else {
            pendingRewards[msg.sender][poolId] = reward - totalRewards;
            totalRewardsPerPool[poolId] = 0;
            emit RewardPaid(msg.sender, poolId, totalRewards);
            IERC20Upgradeable(pool.rewardToken).safeTransfer(
                msg.sender,
                totalRewards
            );
        }
    }

    function updateReward(
        Pool memory pool,
        UserInfo storage userInfo
    ) internal {
        uint256 stakeTime;
        if (block.timestamp > pool.end) stakeTime = pool.end;
        else stakeTime = block.timestamp;
        uint256 balance = userInfo.balance;
        uint256 lastReward;
        if (balance != 0) {
            lastReward =
                (balance *
                    (((stakeTime - userInfo.lastRewarded) *
                        (pool.rewardRate * PRECISION)) /
                        (RATE_PRECISION * SECONDS_IN_YEAR))) /
                PRECISION;
            userInfo.rewards += lastReward;
        }
        userInfo.lastRewarded = stakeTime;
    }

    /// @dev To stake tokens
    /// @param user The address that stake tokens for.
    /// @param amount The number of tokens to be staked.
    /// @param poolId The id of the pool in which tokens should be staked.
    function _stake(address user, uint256 amount, uint256 poolId) internal {
        // Validate
        require(amount > 0, "Cannot stake 0");
        Pool memory pool = pools[poolId];
        UserInfo storage userInfo = userInfoPerPool[poolId][user];
        require(pool.start <= block.timestamp, "Staking not started");
        require(!isPaused[poolId], "Staking Paused");
        require(block.timestamp < pool.end, "Staking Period is over");
        // Update values before staking
        updateReward(pool, userInfo);

        // Stake
        userInfo.balance += amount;
        pools[poolId].totalAmount += amount;
        IERC20Upgradeable(pool.stakingToken).safeTransferFrom(
            msg.sender,
            address(this),
            amount
        );
        emit Staked(user, amount, poolId);
    }

    //Public functions
    function initialize() public initializer {
        __UUPSUpgradeable_init();
        __Ownable_init();
    }

    // View Functions

    /// @dev To get rewards for a particular address for a particular pool
    /// @param account The address of the account whose reward is to be fetched
    /// @param poolId The id of the pool from which rewards for the account needs to be fetched
    function getRewardsForAPool(
        address account,
        uint256 poolId
    ) external view returns (uint256) {
        Pool memory pool = pools[poolId];
        UserInfo memory userInfo = userInfoPerPool[poolId][account];
        uint256 stakeTime;
        if (block.timestamp > pool.end) stakeTime = pool.end;
        else stakeTime = block.timestamp;
        uint256 currentReward = (userInfo.balance *
            (((stakeTime - userInfo.lastRewarded) *
                (pool.rewardRate * PRECISION)) /
                (RATE_PRECISION * SECONDS_IN_YEAR))) / PRECISION;
        currentReward += userInfo.rewards;
        return currentReward;
    }

    /// @dev To get the pool for given id
    /// @return Pool which has the details for every pool
    function getPool(uint256 poolId) public view returns (Pool memory) {
        return pools[poolId];
    }

    /// @dev To get the details for all pools
    /// @return pools which has the details for every pool
    function getPools() public view returns (Pool[] memory, string[] memory) {
        uint256 _totalPools = totalPools;
        Pool[] memory _pools = new Pool[](_totalPools);
        string[] memory symbols = new string[](_totalPools);
        for (uint256 i; i != _totalPools; ++i) {
            _pools[i] = pools[i];
            string memory stakingTokenSymbol = IERC20Metadata(
                _pools[i].stakingToken
            ).symbol();
            string memory rewardTokenSymbol = IERC20Metadata(
                _pools[i].rewardToken
            ).symbol();
            symbols[i] = string(
                abi.encodePacked(stakingTokenSymbol, "/", rewardTokenSymbol)
            );
        }
        return (_pools, symbols);
    }

    function getBalances(
        address user
    ) external view returns (uint256[] memory) {
        uint256 _totalPools = totalPools;
        uint256[] memory _balances = new uint256[](_totalPools);
        for (uint256 i; i != _totalPools; ++i)
            _balances[i] = userInfoPerPool[i][user].balance;
        return _balances;
    }

    //Only Owner functions

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}

    /// @dev To create a staking pool
    /// @param stakingToken Address of the token that will be staked
    /// @param rewardToken Address of the token that will be given as reward
    /// @param rewardRate Rate at which the rewards will be calculated yearly and then multiplied by 100
    /// @param start Start time of the staking pool
    /// @param end Ending time for the staking pool
    function createPool(
        address stakingToken,
        address rewardToken,
        uint256 rewardRate,
        uint256 start,
        uint256 end
    ) external onlyOwner {
        uint256 _totalPools = totalPools;
        require(start < end, "TIME");
        require(stakingToken != rewardToken, "SAME");
        pools[_totalPools] = Pool({
            stakingToken: stakingToken,
            rewardToken: rewardToken,
            rewardRate: rewardRate * 100,
            totalAmount: 0,
            start: start,
            end: end
        });
        totalPools = _totalPools + 1;
    }

    /// @dev To pause or resume a particular staking pool
    /// @param poolId The id of the staking pool that should be paused or resumed
    /// @param pause The boolean where passing true means pause the pool
    ///              and passing false means resume the pool
    function setJobState(uint256 poolId, bool pause) external onlyOwner {
        isPaused[poolId] = pause;
    }

    /// @dev To deposit reward tokens that will be given to the stakers.
    /// @param poolId The id of the pool in which rewards should be allocated
    /// @param amount The value of tokens that should be added to give out as rewards.
    function depositRewards(uint256 poolId, uint256 amount) external {
        totalRewardsPerPool[poolId] += amount;
        emit RewardsDeposited(msg.sender, poolId, amount);
        IERC20Upgradeable(pools[poolId].rewardToken).safeTransferFrom(
            msg.sender,
            address(this),
            amount
        );
    }

    /// @dev To withdraw the extra rewards that remains on the contract
    ///      and can only be called by owner of this contract.
    /// @param poolId The id of the pool in which rewards should be withdrawn
    /// @param amount The value of tokens that should be removed from the contract.
    /// @param receiver The address where the withdrawn tokens should be sent
    function withdrawRewards(
        uint256 poolId,
        uint256 amount,
        address receiver
    ) external onlyOwner {
        // Reduce totalRewards by amount.
        // Owner cannot withdraw more rewards than they have deposited.
        totalRewardsPerPool[poolId] -= amount;
        emit RewardsWithdrawn(amount, poolId);
        IERC20Upgradeable(pools[poolId].rewardToken).safeTransfer(
            receiver,
            amount
        );
    }
}

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

pragma solidity ^0.8.0;

/**
 * @dev Tooling for timepoints, timers and delays
 */
library TimersUpgradeable {
    struct Timestamp {
        uint64 _deadline;
    }

    function getDeadline(Timestamp memory timer) internal pure returns (uint64) {
        return timer._deadline;
    }

    function setDeadline(Timestamp storage timer, uint64 timestamp) internal {
        timer._deadline = timestamp;
    }

    function reset(Timestamp storage timer) internal {
        timer._deadline = 0;
    }

    function isUnset(Timestamp memory timer) internal pure returns (bool) {
        return timer._deadline == 0;
    }

    function isStarted(Timestamp memory timer) internal pure returns (bool) {
        return timer._deadline > 0;
    }

    function isPending(Timestamp memory timer) internal view returns (bool) {
        return timer._deadline > block.timestamp;
    }

    function isExpired(Timestamp memory timer) internal view returns (bool) {
        return isStarted(timer) && timer._deadline <= block.timestamp;
    }

    struct BlockNumber {
        uint64 _deadline;
    }

    function getDeadline(BlockNumber memory timer) internal pure returns (uint64) {
        return timer._deadline;
    }

    function setDeadline(BlockNumber storage timer, uint64 timestamp) internal {
        timer._deadline = timestamp;
    }

    function reset(BlockNumber storage timer) internal {
        timer._deadline = 0;
    }

    function isUnset(BlockNumber memory timer) internal pure returns (bool) {
        return timer._deadline == 0;
    }

    function isStarted(BlockNumber memory timer) internal pure returns (bool) {
        return timer._deadline > 0;
    }

    function isPending(BlockNumber memory timer) internal view returns (bool) {
        return timer._deadline > block.number;
    }

    function isExpired(BlockNumber memory timer) internal view returns (bool) {
        return isStarted(timer) && timer._deadline <= block.number;
    }
}

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

pragma solidity ^0.8.0;

import "./math/MathUpgradeable.sol";

/**
 * @dev Collection of functions related to array types.
 */
library ArraysUpgradeable {
    /**
     * @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 = MathUpgradeable.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;
        }
    }
}

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

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.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * 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}.
     *
     * 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 default value returned by this function, unless
     * it's 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:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, 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}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, 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}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, 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) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, 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) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * 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:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, 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;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _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;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _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 Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - 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 {}
}

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

pragma solidity ^0.8.0;

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

/**
 * @dev Extension of {ERC20} that allows token holders to destroy both their own
 * tokens and those that they have an allowance for, in a way that can be
 * recognized off-chain (via event analysis).
 */
abstract contract ERC20BurnableUpgradeable is Initializable, ContextUpgradeable, ERC20Upgradeable {
    function __ERC20Burnable_init() internal onlyInitializing {
        __Context_init_unchained();
        __ERC20Burnable_init_unchained();
    }

    function __ERC20Burnable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Destroys `amount` tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 amount) public virtual {
        _burn(_msgSender(), amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, deducting from the caller's
     * allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `amount`.
     */
    function burnFrom(address account, uint256 amount) public virtual {
        uint256 currentAllowance = allowance(account, _msgSender());
        require(currentAllowance >= amount, "ERC20: burn amount exceeds allowance");
        unchecked {
            _approve(account, _msgSender(), currentAllowance - amount);
        }
        _burn(account, amount);
    }
    uint256[50] private __gap;
}

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

pragma solidity ^0.8.0;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafeCastUpgradeable {
    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128) {
        require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
        return int128(value);
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64) {
        require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
        return int64(value);
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32) {
        require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
        return int32(value);
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16) {
        require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
        return int16(value);
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8) {
        require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
        return int8(value);
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface IStaticJobs {
    event JobExecuted(bytes32 jobHash);
    event GasDeposited(address indexed depositor, uint256 value);
    event GasWithdrawn(address indexed withdrawer, uint256 value);
    event JobRegistered(
        bytes[] jobInfo,
        address[] targetAddresses,
        bytes32 jobHash,
        string name,
        string ipfsForJobDetails
    );
    event JobToggledByCreator(bytes32 jobHash, uint256 toggle);

    function registerJob(
        bytes[] calldata _jobInfo,
        address[] calldata _targetAddresses,
        string memory _name,
        string memory _ipfsForJobDetails
    ) external;

    function executeJob(
        bytes[] memory _jobInfo,
        address[] memory _targetAddresses
    ) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControlUpgradeable {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

File 47 of 177 : IBeacon.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.0;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeacon {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {BeaconProxy} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface ISteerGovernance {
    event CancelledByInternalGovernance(
        address[] targets,
        uint256[] values,
        bytes[] calldatas,
        bytes32 descriptionHash
    );

    function setInternalGovernanceTimeLockOnce(
        address _internalGovernanceTimeLock
    ) external;

    function cancelByInternalGovernance(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "../interfaces/IGasVault.sol";
import "../interfaces/IImplementation.sol";

// Created just to have something to run orchestrator actions on so testing is easy
contract DummyContract is
    Initializable,
    OwnableUpgradeable,
    UUPSUpgradeable,
    IImplementation
{
    uint256 public num;
    uint256[] public gasInTransaction;
    IGasVault public gasVault;

    function changeNum(uint256 newNum) public {
        assert(newNum != 196);
        num = newNum;
    }

    function initialize(
        address,
        address,
        address _steer,
        bytes calldata
    ) public initializer {
        __UUPSUpgradeable_init();
        __Ownable_init();
        _transferOwnership(_steer);
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}

    function recordGas() public {
        gasInTransaction.push(gasleft());
    }

    function setNumTo50() public {
        num = 50;
    }

    function setNumToMultiple(uint256 firstNum, uint256 secondNum) external {
        num = firstNum * secondNum;
    }

    function getPosition(
        address owner,
        int24 bottomTick,
        int24 topTick
    ) public pure returns (bytes32 key) {
        assembly {
            key := or(
                shl(24, or(shl(24, owner), and(bottomTick, 0xFFFFFF))),
                and(topTick, 0xFFFFFF)
            )
        }
        return key;
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = owner();
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = 6666;
        feeValues[1] = 3337;
        return (0, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }
}

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

import "./SilverSwapBaseLiquidityManager.sol";

contract SilverSwapMultiPositionLiquidityManager is
    SilverSwapBaseLiquidityManager
{
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

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

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);
            }
        }
    }
}

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

import "./BeamBaseLiquidityManager.sol";

contract BeamMultiPositionLiquidityManager is BeamBaseLiquidityManager {
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage

    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions() external view returns (int24[] memory, int24[] memory, uint16[] memory) {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (oldPositions.lowerTick, oldPositions.upperTick, oldPositions.relativeWeight);
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(uint256 totalWeight, TendLiquidityPositions memory newPositions, bytes calldata timeSensitiveData)
        external
        onlyRole(MANAGER_ROLE)
        whenNotPaused
    {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(timeSensitiveData, (int256, uint160));

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick,,,,) = pool.globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne ? tendCache.swapAmount : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96,,,,,) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(tendCache.sqrtPriceX96, tendCache.balance0, tendCache.balance1, totalSupply());

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity,,,,) = _position(_positions[i].lowerTick, _positions[i].upperTick);

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(_positions[i].lowerTick, _positions[i].upperTick, 0, "");
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts() public view override returns (uint256 total0, uint256 total1) {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96,,,,,) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(address(this));
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity,,, uint256 fees0, uint256 fees1) =
                _position(_positions[i].lowerTick, _positions[i].upperTick);

            (uint256 amt0, uint256 amt1) = LiquidityAmounts.getAmountsForLiquidity(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                uint128(liquidity)
            );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(uint160 sqrtPriceX96, uint256 t0ToDeposit, uint256 t1ToDeposit, int256 swapAmount) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick
                        && _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts.getAmountsForLiquidity(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                uint128(PRECISION * _positions[i].relativeWeight)
            );
            // No safecast here--an overflow will lead to an incorrect number,
            // which will either (usually) revert, or cause a harmless liquidity miscalculation.
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount =
                totalT0Requested > 0 ? FullMath.mulDiv(positionT0Requested[i], t0ToDeposit, totalT0Requested) : 0;
            uint256 positionT1Amount =
                totalT1Requested > 0 ? FullMath.mulDiv(positionT1Requested[i], t1ToDeposit, totalT1Requested) : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(address(this), address(this), _positions[i].lowerTick, _positions[i].upperTick, liquidity, "");
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(uint256 shares, uint256 totalShares) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity,,,,) = _position(lowerTick, upperTick);

                liquidityToBurn = uint128(FullMath.mulDiv(totalPositionLiquidity, shares, totalShares));
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(lowerTick, upperTick, liquidityToBurn, "");

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) =
                    pool.collect(address(this), lowerTick, upperTick, type(uint128).max, type(uint128).max);

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(address(this));
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(address(this));
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0) {
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        }
        if (fees1 > 0) {
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        }
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] + FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] + FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

File 53 of 177 : IImplementation.sol
// SPDX-License-Identifier: BSL-1.1

pragma solidity 0.8.12;

interface IImplementation {
    /// @dev To initialize a vault.
    function initialize(
        address _vaultManager,
        address _orchestrator,
        address _steer,
        bytes calldata _params
    ) external;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.12;
pragma abicoder v2; //Used this because function getAssetSymbols uses string[2]

import { IStrategyRegistry } from "./IStrategyRegistry.sol";
import { IOrchestrator } from "./IOrchestrator.sol";

interface IVaultRegistry {
    /**
     * PendingApproval: strategy is submitted but has not yet been approved by the owner
     * PendingThreshold: strategy is approved but has not yet reached the threshold of TVL required
     * Paused: strategy was active but something went wrong, so now it's paused
     * Active: strategy is active and can be used
     * Retired: strategy is retired and can no longer be used
     */
    enum VaultState {
        PendingApproval,
        PendingThreshold,
        Paused,
        Active,
        Retired
    }

    /**
     * @dev all necessary data for vault. Name and symbol are stored in vault's ERC20. Owner is stored with tokenId in StrategyRegistry.
     * tokenId: NFT identifier number
     * vaultAddress: address of vault this describes
     * state: state of the vault.
     */
    struct VaultData {
        VaultState state;
        uint256 tokenId; //NFT ownership of this vault and all others that use vault's exec bundle
        uint256 vaultID; //unique identifier for this vault and strategy token id
        string payloadIpfs;
        address vaultAddress;
        string beaconName;
    }

    /// @dev Vault creation event
    /// @param deployer The address of the deployer
    /// @param vault The address of the vault
    /// @param tokenId ERC721 token id for the vault
    /// @param vaultManager is the address which will manage the vault being created
    event VaultCreated(
        address deployer,
        address vault,
        string beaconName,
        uint256 indexed tokenId,
        address vaultManager
    );

    /// @dev Vault state change event
    /// @param vault The address of the vault
    /// @param newState The new state of the vault
    event VaultStateChanged(address indexed vault, VaultState newState);

    // Total vault count.
    function totalVaultCount() external view returns (uint256);

    function whitelistRegistry() external view returns (address);

    function orchestrator() external view returns (IOrchestrator);

    function beaconAddresses(string calldata) external view returns (address);

    function beaconTypes(address) external view returns (string memory);

    // Interface for the strategy registry
    function strategyRegistry() external view returns (IStrategyRegistry);

    /// @dev intializes the vault registry
    /// @param _orchestrator The address of the orchestrator
    /// @param _strategyRegistry The address of the strategy registry
    /// @param _whitelistRegistry The address of the whitelist registry
    function initialize(
        address _orchestrator,
        address _strategyRegistry,
        address _whitelistRegistry
    ) external;

    /// @dev Registers a beacon associated with a new vault type
    /// @param _name The name of the vault type this beacon will be using
    /// @param _address The address of the upgrade beacon
    /// @param _ipfsConfigForBeacon IPFS hash for the config of this beacon
    function registerBeacon(
        string calldata _name,
        address _address,
        string calldata _ipfsConfigForBeacon
    ) external;

    /// @dev Deploy new beacon for a new vault type AND register it
    /// @param _address The address of the implementation for the beacon
    /// @param _name The name of the beacon (identifier)
    /// @param _ipfsConfigForBeacon IPFS hash for the config of this beacon
    function deployAndRegisterBeacon(
        address _address,
        string calldata _name,
        string calldata _ipfsConfigForBeacon
    ) external returns (address);

    /// @dev Removes a beacon associated with a vault type
    /// @param _name The name of the beacon (identifier)
    /// @dev This will stop the creation of more vaults of the type provided
    function deregisterBeacon(string calldata _name) external;

    /// @dev Creates a new vault with the given strategy
    /// @dev Registers an execution bundle, mints an NFT and mappings it to execution bundle and it's details.
    /// @param _params is extra parameters in vault.
    /// @param _tokenId is the NFT of the execution bundle this vault will be using.
    /// @param _beaconName beacon identifier of vault type to be created
    /// @dev owner is set as msg.sender.
    function createVault(
        bytes memory _params,
        uint256 _tokenId,
        string memory _beaconName,
        address _vaultManager,
        string memory strategyData
    ) external returns (address);

    /// @dev Updates the vault state and emits a VaultStateChanged event
    /// @param _vault The address of the vault
    /// @param _newState The new state of the vault
    /// @dev This function is only available to the registry owner
    function updateVaultState(address _vault, VaultState _newState) external;

    /// @dev Retrieves the creator of a given vault
    /// @param _vault The address of the vault
    /// @return The address of the creator
    function getStrategyCreatorForVault(
        address _vault
    ) external view returns (address);

    /// @dev This function is only available to the pauser role
    function pause() external;

    function unpause() external;

    /// @dev Retrieves the details of a given vault by address
    /// @param _address The address of the vault
    /// @return The details of the vault
    function getVaultDetails(
        address _address
    ) external view returns (VaultData memory);

    /// @dev Retrieves the vault count by vault token id
    /// @param _tokenId The token id of the vault
    /// @return The count of the vault
    function getVaultCountByStrategyId(
        uint256 _tokenId
    ) external view returns (uint256);

    /// @dev Retrieves the vault by vault token id and vault index
    /// @param _tokenId The token id of the vault
    /// @param _vaultId The index of the vault
    /// @return Vault details
    function getVaultByStrategyAndIndex(
        uint256 _tokenId,
        uint256 _vaultId
    ) external view returns (VaultData memory);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface IRunnerRewarder {
    /// @dev Emitted after a successful token claim
    /// @param to recipient of claim
    /// @param amount of tokens claimed
    event Claim(address indexed to, uint256 amount);

    function setRewardOrchestrator(address _rewardOrchestrator) external;

    /**
     * @param hash -- proposed new merkle root hash.
     */
    function changeMerkleRootHash(bytes32 hash) external;

    function claim(uint256 amount, bytes32[] calldata proof) external;
}

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

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 onlyInitializing {
        __Context_init_unchained();
        __Ownable_init_unchained();
    }

    function __Ownable_init_unchained() internal onlyInitializing {
        _transferOwnership(_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 {
        _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);
    }
    uint256[49] private __gap;
}

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

import "./HenjinBaseLiquidityManager.sol";

contract HenjinMultiPositionLiquidityManager is HenjinBaseLiquidityManager {
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

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

import "./KimBaseLiquidityManager.sol";

contract KimMultiPositionLiquidityManager is KimBaseLiquidityManager {
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

File 59 of 177 : Constants.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0 <0.9.0;

/// @title Contains common constants for Algebra contracts
/// @dev Constants moved to the library, not the base contract, to further emphasize their constant nature
library Constants {
  uint8 internal constant RESOLUTION = 96;
  uint256 internal constant Q96 = 1 << 96;
  uint256 internal constant Q128 = 1 << 128;

  uint24 internal constant FEE_DENOMINATOR = 1e6;
  uint16 internal constant FLASH_FEE = 0.01e4; // fee for flash loan in hundredths of a bip (0.01%)
  uint16 internal constant INIT_DEFAULT_FEE = 0.05e4; // init default fee value in hundredths of a bip (0.05%)
  uint16 internal constant MAX_DEFAULT_FEE = 5e4; // max default fee value in hundredths of a bip (5%)

  int24 internal constant INIT_DEFAULT_TICK_SPACING = 60;
  int24 internal constant MAX_TICK_SPACING = 500;
  int24 internal constant MIN_TICK_SPACING = 1;

  // the frequency with which the accumulated community fees are sent to the vault
  uint32 internal constant COMMUNITY_FEE_TRANSFER_FREQUENCY = 8 hours;

  // max(uint128) / (MAX_TICK - MIN_TICK)
  uint128 internal constant MAX_LIQUIDITY_PER_TICK = 191757638537527648490752896198553;

  uint16 internal constant MAX_COMMUNITY_FEE = 1e3; // 100%
  uint256 internal constant COMMUNITY_FEE_DENOMINATOR = 1e3;
  // role that can change settings in pools
  bytes32 internal constant POOLS_ADMINISTRATOR_ROLE = keccak256('POOLS_ADMINISTRATOR');
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.12;
import { ITreasuryVester, ISteerToken } from "./interfaces/ITreasuryVester.sol";

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

contract TreasuryVester is ITreasuryVester {
    using SafeERC20 for IERC20;

    address public immutable steerToken;
    uint256 public immutable vestingAmount;
    uint256 public immutable vestingDuration;
    uint256 public immutable vestingCliff;
    uint256 public immutable vestingEnd;

    address public recipient;
    address public immutable owner;
    uint256 public lastUpdate;

    constructor(
        address steerToken_,
        address recipient_,
        address _owner, // Intended to be multisig. This address can cut off funding if user leaves Steer.
        uint256 vestingAmount_,
        uint256 vestingBegin_,
        uint256 vestingCliff_,
        uint256 vestingEnd_
    ) {
        require(vestingCliff_ >= vestingBegin_, "C");
        require(vestingEnd_ > vestingCliff_, "E");

        steerToken = steerToken_;
        recipient = recipient_;
        owner = _owner;

        vestingAmount = vestingAmount_;
        vestingCliff = vestingCliff_;
        vestingEnd = vestingEnd_;
        vestingDuration = vestingEnd_ - vestingBegin_;
        lastUpdate = vestingBegin_;
    }

    /// @dev Change the recipient address. Can only be called by current recipient.
    /// @param _recipient is the new recipient address
    function setRecipient(address _recipient) external {
        require(_recipient != address(0), "Zero address, Invalid!");
        require(
            // Make sure current recipient is sending this
            msg.sender == recipient,
            "R"
        );
        recipient = _recipient;
        getVotingPowerForVestedTokens();
    }

    function claim() external {
        require(msg.sender == recipient, "R");
        _claim();
    }

    /// @dev If the user leaves steer then steer can cut off the user's funds from that point on.
    function cutOffFunds() external {
        require(msg.sender == owner, "Unauthorized");
        if (block.timestamp >= vestingCliff) {
            _claim();
        }

        IERC20(steerToken).safeTransfer(
            owner,
            IERC20(steerToken).balanceOf(address(this))
        );
    }

    /// @dev User can call this function to get the voting power of the tokens that are vested.
    function getVotingPowerForVestedTokens() public {
        ISteerToken(steerToken).delegate(recipient);
    }

    /// @dev Use this function to claim the claimable tokens.
    function _claim() internal {
        require(block.timestamp >= vestingCliff, "T");
        uint256 amount;
        if (block.timestamp >= vestingEnd) {
            amount = IERC20(steerToken).balanceOf(address(this));
        } else {
            // Amount = accrued earnings since last update
            amount =
                (vestingAmount * (block.timestamp - lastUpdate)) /
                vestingDuration;

            // Update lastUpdate to current timestamp
            lastUpdate = block.timestamp;
        }
        IERC20(steerToken).safeTransfer(recipient, amount);
    }
}

File 61 of 177 : IUniswapV3Pool.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import './pool/IUniswapV3PoolImmutables.sol';
import './pool/IUniswapV3PoolState.sol';
import './pool/IUniswapV3PoolDerivedState.sol';
import './pool/IUniswapV3PoolActions.sol';
import './pool/IUniswapV3PoolOwnerActions.sol';
import './pool/IUniswapV3PoolEvents.sol';

/// @title The interface for a Uniswap V3 Pool
/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform
/// to the ERC20 specification
/// @dev The pool interface is broken up into many smaller pieces
interface IUniswapV3Pool is
    IUniswapV3PoolImmutables,
    IUniswapV3PoolState,
    IUniswapV3PoolDerivedState,
    IUniswapV3PoolActions,
    IUniswapV3PoolOwnerActions,
    IUniswapV3PoolEvents
{

}

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6;
pragma abicoder v2;

interface IFeeManager {
    struct Fee {
        string feeIdentifier;
        uint256 feeValue;
    }

    function setFeeAndWithdrawalPermission(
        address vault,
        string[] memory feeIdentifier,
        uint256[] memory feeValue,
        address[] memory withdrawer
    ) external;

    function setDefaultFeeAndWithdrawalPermission(
        address vault,
        uint256 totalVaultFees,
        string[] memory feeIdentifier,
        uint256[] memory feeValue,
        address[] memory withdrawer
    ) external;

    function withdrawFee(address vault, string memory feeIdentifier) external;

    function getFees(address vault) external view returns (Fee[] memory);

    function vaultTotalFees(address vault) external view returns (uint256);

    function setMigratedVaultFeeAndWithdrawalPermission() external;

    function withdrawalPermissions(
        address vault,
        string memory feeIdentifier
    ) external view returns (address);
}

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

pragma solidity ^0.8.0;

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

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

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    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));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    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");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

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

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

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // 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 cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract SwapsicleBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0xA09BAbf9A48003ae9b9333966a8Bda94d820D0d9);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title An interface for a contract that is capable of deploying Algebra plugins
/// @dev Such a factory is needed if the plugin should be automatically created and connected to each new pool
interface IAlgebraPluginFactory {
  /// @notice Deploys new plugin contract for pool
  /// @param pool The address of the pool for which the new plugin will be created
  /// @return New plugin address
  function createPlugin(address pool) external returns (address);
}

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

pragma solidity ^0.8.0;

import "../IERC721Upgradeable.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721EnumerableUpgradeable is IERC721Upgradeable {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}

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

pragma solidity ^0.8.0;

import "./draft-IERC20PermitUpgradeable.sol";
import "../ERC20Upgradeable.sol";
import "../../../utils/cryptography/draft-EIP712Upgradeable.sol";
import "../../../utils/cryptography/ECDSAUpgradeable.sol";
import "../../../utils/CountersUpgradeable.sol";
import "../../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * _Available since v3.4._
 */
abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IERC20PermitUpgradeable, EIP712Upgradeable {
    using CountersUpgradeable for CountersUpgradeable.Counter;

    mapping(address => CountersUpgradeable.Counter) private _nonces;

    // solhint-disable-next-line var-name-mixedcase
    bytes32 private _PERMIT_TYPEHASH;

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
     */
    function __ERC20Permit_init(string memory name) internal onlyInitializing {
        __Context_init_unchained();
        __EIP712_init_unchained(name, "1");
        __ERC20Permit_init_unchained(name);
    }

    function __ERC20Permit_init_unchained(string memory name) internal onlyInitializing {
        _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");}

    /**
     * @dev See {IERC20Permit-permit}.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSAUpgradeable.recover(hash, v, r, s);
        require(signer == owner, "ERC20Permit: invalid signature");

        _approve(owner, spender, value);
    }

    /**
     * @dev See {IERC20Permit-nonces}.
     */
    function nonces(address owner) public view virtual override returns (uint256) {
        return _nonces[owner].current();
    }

    /**
     * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view override returns (bytes32) {
        return _domainSeparatorV4();
    }

    /**
     * @dev "Consume a nonce": return the current value and increment.
     *
     * _Available since v4.1._
     */
    function _useNonce(address owner) internal virtual returns (uint256 current) {
        CountersUpgradeable.Counter storage nonce = _nonces[owner];
        current = nonce.current();
        nonce.increment();
    }
    uint256[49] private __gap;
}

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

import "./IntegralBaseLiquidityManager.sol";

contract IntegralMultiPositionLiquidityManager is
    IntegralBaseLiquidityManager
{
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

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

pragma solidity ^0.8.0;

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

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721Upgradeable is IERC165Upgradeable {
    /**
     * @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;
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract WasabeeIntegralBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0x7d53327D78EFD0b463bd8d7dc938C52402323b95);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

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

import { IDynamicJobs } from "./interfaces/IDynamicJobs.sol";
import { IKeeperRegistry } from "./interfaces/IKeeperRegistry.sol";
import { IGasVault } from "./interfaces/IGasVault.sol";
import { IOrchestrator } from "./interfaces/IOrchestrator.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract DynamicJobs is Initializable, OwnableUpgradeable, IDynamicJobs {
    // Storage

    address public keeperRegistry;
    address public orchestrator;
    address public creator;
    uint256 public gasBalance;
    address public gasVault;

    /// @dev The mapping of jobState has 3 possiblities
    ///      1) 0 means not registered
    ///      2) 1 means registered and paused
    ///      3) 2 means unpaused and registered (active)
    mapping(bytes32 => uint256) public jobState;

    // Constructor

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    function initialize(
        address _orchestrator,
        address, // Steer multisig not used here
        address, // Internal governance not used here
        bytes calldata _params
    ) external initializer {
        address _creator = abi.decode(_params, (address));

        // Fetch gas vault and keeper registry from orchestrator
        keeperRegistry = IOrchestrator(_orchestrator).keeperRegistry();
        gasVault = IOrchestrator(_orchestrator).gasVault();

        __Ownable_init();
        creator = _creator;
        orchestrator = _orchestrator;
    }

    /// @dev Use this function to register jobs.
    /// @param _userProvidedData are the calldatas that are provided by the user at the registration time.
    /// @param _targetAddresses are the addresses of the contracts on which the jobs needs to be executed.
    /// @param _name should be the name of the job.
    /// @param _ipfsForJobDetails is the ipfs hash containing job details like the interval for job execution.
    function registerJob(
        bytes[] calldata _userProvidedData,
        address[] calldata _targetAddresses,
        string calldata _name,
        string calldata _ipfsForJobDetails
    ) external {
        // Validate param length
        require(
            _userProvidedData.length == _targetAddresses.length,
            "Wrong Address Count"
        );

        // Only vault owner can register jobs for this vault
        require(creator == msg.sender, "Unauthorized");

        // Record job hash
        bytes32 jobHash = keccak256(
            abi.encode(_userProvidedData, _targetAddresses)
        );

        // Job is currently unpaused
        jobState[jobHash] = 2;

        // Emit job details so that they can be used offchain
        emit JobRegistered(
            _userProvidedData,
            _targetAddresses,
            jobHash,
            _name,
            _ipfsForJobDetails
        );
    }

    /// @dev Use this function to register jobs and deposit gas in one call
    /// @dev Send the amount of gas that is needed to be deposited as msg.value.
    /// @param _userProvidedData are the calldatas that are provided by the user at the registration time.
    /// @param _targetAddresses are the addresses of the contracts on which the jobs needs to be executed.
    /// @param _name is the name of the job.
    /// @param _ipfsForJobDetails is the ipfs hash containing job details like the interval for job execution.
    function registerJobAndDepositGas(
        bytes[] calldata _userProvidedData,
        address[] calldata _targetAddresses,
        string calldata _name,
        string calldata _ipfsForJobDetails
    ) external payable {
        // Register job
        require(
            _userProvidedData.length == _targetAddresses.length,
            "Wrong Address Count"
        );
        require(creator == msg.sender, "Unauthorized");
        bytes32 jobHash = keccak256(
            abi.encode(_userProvidedData, _targetAddresses)
        );
        jobState[jobHash] = 2;
        emit JobRegistered(
            _userProvidedData,
            _targetAddresses,
            jobHash,
            _name,
            _ipfsForJobDetails
        );

        // Deposit gas
        IGasVault(gasVault).deposit{ value: msg.value }(address(this));
    }

    /// @dev Use this function to execute Jobs.
    /// @dev Only Orchestrator can call this function.
    /// @param _userProvidedData are the calldatas that are provided by the user at the registration time.
    /// @param _strategyProvidedData are the encoded parameters sent on the time of creation or execution of action in orchestrator according to the strategy.
    /// @param _targetAddresses are the addresses of the contracts on which the jobs needs to be executed.
    function executeJob(
        address[] calldata _targetAddresses,
        bytes[] calldata _userProvidedData,
        bytes[] calldata _strategyProvidedData
    ) external {
        bytes32 _jobHash = keccak256(
            abi.encode(_userProvidedData, _targetAddresses)
        );

        // Ensure passed params match user registered job
        require(jobState[_jobHash] == 2, "Paused or Not Registered");

        // Ensure that job is not paused
        require(msg.sender == orchestrator, "Unauthorized");

        uint256 jobCount = _targetAddresses.length;

        bytes memory completeData;
        bool success;
        for (uint256 i; i != jobCount; ++i) {
            completeData = abi.encodePacked(
                _userProvidedData[i],
                _strategyProvidedData[i]
            );
            (success, ) = _targetAddresses[i].call(completeData);

            // Revert if this method failed, thus reverting all methods in this job
            require(success);
        }
        emit JobExecuted(_jobHash, msg.sender);
    }

    /// @dev Use this function to pause or unpause a job
    /// @param _jobHash is the keccak of encoded parameters and target addresses
    /// @param _toggle pass 1 to pause the job and pass 2 to unpause the job
    function setJobState(bytes32 _jobHash, uint256 _toggle) external {
        require(creator == msg.sender, "Access Denied");
        require(_toggle == 1 || _toggle == 2, "Invalid");
        jobState[_jobHash] = _toggle;
        emit JobToggledByCreator(_jobHash, _toggle);
    }

    /// @dev Use this function to withdraw gas associated to this vault
    /// @dev Only creator of this vault can call this function
    /// @param _amount is the amount of ether in wei that creator of this contract wants to pull out
    /// @param to is the address at which the creator wants to pull the deposited ether out
    function withdrawGas(uint256 _amount, address payable to) external {
        require(msg.sender == creator, "Not Creator");
        IGasVault(gasVault).withdraw(_amount, to);
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

/**
 * Used only as a 0.7.6 registry for the UniLiquidityManager.
 */
interface IBareVaultRegistry8 {
    /**
     * PendingApproval: strategy is submitted but has not yet been approved by the owner
     * PendingThreshold: strategy is approved but has not yet reached the threshold of TVL required
     * Paused: strategy was active but something went wrong, so now it's paused
     * Active: strategy is active and can be used
     * Retired: strategy is retired and can no longer be used
     */
    enum VaultState {
        PendingApproval,
        PendingThreshold,
        Paused,
        Active,
        Retired
    }

    /**
     * @dev all necessary data for vault. Name and symbol are stored in vault's ERC20. Owner is stored with tokenId in StrategyRegistry.
     * tokenId: NFT identifier number
     * vaultAddress: address of vault this describes
     * state: state of the vault.
     */
    struct VaultData {
        VaultState state;
        uint256 tokenId; //NFT ownership of this vault and all others that use vault's exec bundle
        uint256 vaultID; //unique identifier for this vault and strategy token id
        string payloadIpfs;
        address vaultAddress;
        string beaconName;
    }

    /// @notice Retrieves the creator of a given vault
    /// @param _vault The address of the vault
    /// @return The address of the creator
    function getStrategyCreatorForVault(
        address _vault
    ) external view returns (address);

    //Total vault count
    function totalVaultCount() external view returns (uint256);

    function whitelistRegistry() external view returns (address);

    function doISupportInterface(
        bytes4 interfaceId
    ) external view returns (bool);

    function feeManager() external view returns (address);

    /// @dev Retrieves the details of a given vault by address
    /// @param _address The address of the vault
    /// @return The details of the vault
    function getVaultDetails(
        address _address
    ) external view returns (VaultData memory);

    function owner() external view returns (address);

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

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma abicoder v2;

import '../libraries/AdaptiveFee.sol';

interface IDataStorageOperator {
  event FeeConfiguration(AdaptiveFee.Configuration feeConfig);

  /**
   * @notice Returns data belonging to a certain timepoint
   * @param index The index of timepoint in the array
   * @dev There is more convenient function to fetch a timepoint: getTimepoints(). Which requires not an index but seconds
   * @return initialized Whether the timepoint has been initialized and the values are safe to use,
   * blockTimestamp The timestamp of the observation,
   * tickCumulative The tick multiplied by seconds elapsed for the life of the pool as of the timepoint timestamp,
   * secondsPerLiquidityCumulative The seconds per in range liquidity for the life of the pool as of the timepoint timestamp,
   * volatilityCumulative Cumulative standard deviation for the life of the pool as of the timepoint timestamp,
   * averageTick Time-weighted average tick,
   * volumePerLiquidityCumulative Cumulative swap volume per liquidity for the life of the pool as of the timepoint timestamp
   */
  function timepoints(
    uint256 index
  )
    external
    view
    returns (
      bool initialized,
      uint32 blockTimestamp,
      int56 tickCumulative,
      uint160 secondsPerLiquidityCumulative,
      uint88 volatilityCumulative,
      int24 averageTick,
      uint144 volumePerLiquidityCumulative
    );

  /// @notice Initialize the dataStorage array by writing the first slot. Called once for the lifecycle of the timepoints array
  /// @param time The time of the dataStorage initialization, via block.timestamp truncated to uint32
  /// @param tick Initial tick
  function initialize(uint32 time, int24 tick) external;

  /// @dev Reverts if an timepoint at or before the desired timepoint timestamp does not exist.
  /// 0 may be passed as `secondsAgo' to return the current cumulative values.
  /// If called with a timestamp falling between two timepoints, returns the counterfactual accumulator values
  /// at exactly the timestamp between the two timepoints.
  /// @param time The current block timestamp
  /// @param secondsAgo The amount of time to look back, in seconds, at which point to return an timepoint
  /// @param tick The current tick
  /// @param index The index of the timepoint that was most recently written to the timepoints array
  /// @param liquidity The current in-range pool liquidity
  /// @return tickCumulative The cumulative tick since the pool was first initialized, as of `secondsAgo`
  /// @return secondsPerLiquidityCumulative The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of `secondsAgo`
  /// @return volatilityCumulative The cumulative volatility value since the pool was first initialized, as of `secondsAgo`
  /// @return volumePerAvgLiquidity The cumulative volume per liquidity value since the pool was first initialized, as of `secondsAgo`
  function getSingleTimepoint(
    uint32 time,
    uint32 secondsAgo,
    int24 tick,
    uint16 index,
    uint128 liquidity
  ) external view returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulative, uint112 volatilityCumulative, uint256 volumePerAvgLiquidity);

  /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos`
  /// @dev Reverts if `secondsAgos` > oldest timepoint
  /// @param time The current block.timestamp
  /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an timepoint
  /// @param tick The current tick
  /// @param index The index of the timepoint that was most recently written to the timepoints array
  /// @param liquidity The current in-range pool liquidity
  /// @return tickCumulatives The cumulative tick since the pool was first initialized, as of each `secondsAgo`
  /// @return secondsPerLiquidityCumulatives The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo`
  /// @return volatilityCumulatives The cumulative volatility values since the pool was first initialized, as of each `secondsAgo`
  /// @return volumePerAvgLiquiditys The cumulative volume per liquidity values since the pool was first initialized, as of each `secondsAgo`
  function getTimepoints(
    uint32 time,
    uint32[] memory secondsAgos,
    int24 tick,
    uint16 index,
    uint128 liquidity
  )
    external
    view
    returns (
      int56[] memory tickCumulatives,
      uint160[] memory secondsPerLiquidityCumulatives,
      uint112[] memory volatilityCumulatives,
      uint256[] memory volumePerAvgLiquiditys
    );

  /// @notice Returns average volatility in the range from time-WINDOW to time
  /// @param time The current block.timestamp
  /// @param tick The current tick
  /// @param index The index of the timepoint that was most recently written to the timepoints array
  /// @param liquidity The current in-range pool liquidity
  /// @return TWVolatilityAverage The average volatility in the recent range
  /// @return TWVolumePerLiqAverage The average volume per liquidity in the recent range
  function getAverages(
    uint32 time,
    int24 tick,
    uint16 index,
    uint128 liquidity
  ) external view returns (uint112 TWVolatilityAverage, uint256 TWVolumePerLiqAverage);

  /// @notice Writes an dataStorage timepoint to the array
  /// @dev Writable at most once per block. Index represents the most recently written element. index must be tracked externally.
  /// @param index The index of the timepoint that was most recently written to the timepoints array
  /// @param blockTimestamp The timestamp of the new timepoint
  /// @param tick The active tick at the time of the new timepoint
  /// @param liquidity The total in-range liquidity at the time of the new timepoint
  /// @param volumePerLiquidity The gmean(volumes)/liquidity at the time of the new timepoint
  /// @return indexUpdated The new index of the most recently written element in the dataStorage array
  function write(
    uint16 index,
    uint32 blockTimestamp,
    int24 tick,
    uint128 liquidity,
    uint128 volumePerLiquidity
  ) external returns (uint16 indexUpdated);

  /// @notice Changes fee configuration for the pool
  function changeFeeConfiguration(AdaptiveFee.Configuration calldata feeConfig) external;

  /// @notice Calculates gmean(volume/liquidity) for block
  /// @param liquidity The current in-range pool liquidity
  /// @param amount0 Total amount of swapped token0
  /// @param amount1 Total amount of swapped token1
  /// @return volumePerLiquidity gmean(volume/liquidity) capped by 100000 << 64
  function calculateVolumePerLiquidity(uint128 liquidity, int256 amount0, int256 amount1) external pure returns (uint128 volumePerLiquidity);

  /// @return windowLength Length of window used to calculate averages
  function window() external view returns (uint32 windowLength);

  /// @notice Calculates fee based on combination of sigmoids
  /// @param time The current block.timestamp
  /// @param tick The current tick
  /// @param index The index of the timepoint that was most recently written to the timepoints array
  /// @param liquidity The current in-range pool liquidity
  /// @return fee The fee in hundredths of a bip, i.e. 1e-6
  function getFee(uint32 time, int24 tick, uint16 index, uint128 liquidity) external view returns (uint16 fee);
}

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

import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorSettingsUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/compatibility/GovernorCompatibilityBravoUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "./interfaces/ISteerGovernance.sol";

contract SteerGovernance is
    ISteerGovernance,
    Initializable,
    GovernorUpgradeable,
    GovernorSettingsUpgradeable,
    GovernorCompatibilityBravoUpgradeable,
    GovernorVotesUpgradeable,
    GovernorVotesQuorumFractionUpgradeable,
    GovernorTimelockControlUpgradeable,
    OwnableUpgradeable,
    UUPSUpgradeable
{
    address internal internalGovernanceTimeLock;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    function initialize(
        ERC20VotesUpgradeable _token,
        TimelockControllerUpgradeable _timelock
    ) public initializer {
        __Governor_init("SteerGovernance");
        __GovernorSettings_init(
            14400 /* 2 Days of voting delay*/,
            36000 /* 5 Days of voting period */,
            100e18 /* Voters need atleast 100 tokens to vote */
        );
        __GovernorCompatibilityBravo_init();
        __GovernorVotes_init(_token);
        __GovernorVotesQuorumFraction_init(2 /* 2% */);
        __GovernorTimelockControl_init(_timelock);
        __Ownable_init();
        __UUPSUpgradeable_init();
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}

    function votingDelay()
        public
        view
        override(IGovernorUpgradeable, GovernorSettingsUpgradeable)
        returns (uint256)
    {
        return super.votingDelay();
    }

    function votingPeriod()
        public
        view
        override(IGovernorUpgradeable, GovernorSettingsUpgradeable)
        returns (uint256)
    {
        return super.votingPeriod();
    }

    function quorum(
        uint256 blockNumber
    )
        public
        view
        override(IGovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable)
        returns (uint256)
    {
        return super.quorum(blockNumber);
    }

    function getVotes(
        address account,
        uint256 blockNumber
    )
        public
        view
        override(IGovernorUpgradeable, GovernorVotesUpgradeable)
        returns (uint256)
    {
        return super.getVotes(account, blockNumber);
    }

    function state(
        uint256 proposalId
    )
        public
        view
        override(
            GovernorUpgradeable,
            IGovernorUpgradeable,
            GovernorTimelockControlUpgradeable
        )
        returns (ProposalState)
    {
        return super.state(proposalId);
    }

    function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    )
        public
        override(
            GovernorUpgradeable,
            GovernorCompatibilityBravoUpgradeable,
            IGovernorUpgradeable
        )
        returns (uint256)
    {
        return super.propose(targets, values, calldatas, description);
    }

    function proposalThreshold()
        public
        view
        override(GovernorUpgradeable, GovernorSettingsUpgradeable)
        returns (uint256)
    {
        return super.proposalThreshold();
    }

    function _execute(
        uint256 proposalId,
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    )
        internal
        override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
    {
        super._execute(
            proposalId,
            targets,
            values,
            calldatas,
            descriptionHash
        );
    }

    function _cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    )
        internal
        override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
        returns (uint256)
    {
        return super._cancel(targets, values, calldatas, descriptionHash);
    }

    /// @dev Use this function to set address of Internal Governance Timelock.
    /// @dev This function can only be called once while deployment.
    /// @param _internalGovernanceTimeLock is the address of Internal Governance Timelock.
    function setInternalGovernanceTimeLockOnce(
        address _internalGovernanceTimeLock
    ) external onlyOwner {
        require(internalGovernanceTimeLock == address(0), "Already Set");
        internalGovernanceTimeLock = _internalGovernanceTimeLock;
    }

    /// @dev This function can cancel a proposal irrespective of votes on it.
    /// @dev This function can only be called by Internal Governance Timelock.
    ///So only after there are enough votes in Internal Governance this function can be called.
    function cancelByInternalGovernance(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public {
        require(
            msg.sender == internalGovernanceTimeLock,
            "Not Internal Governance"
        );
        _cancel(targets, values, calldatas, descriptionHash);
        emit CancelledByInternalGovernance(
            targets,
            values,
            calldatas,
            descriptionHash
        );
    }

    function _executor()
        internal
        view
        override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
        returns (address)
    {
        return super._executor();
    }

    function supportsInterface(
        bytes4 interfaceId
    )
        public
        view
        override(
            GovernorUpgradeable,
            IERC165Upgradeable,
            GovernorTimelockControlUpgradeable
        )
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

// SPDX-License-Identifier: BUSL-1.1
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

pragma solidity 0.8.12;

contract TestERC20 is ERC20 {
    address public owner;

    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        owner = msg.sender;
        _mint(msg.sender, 10 ** 50);
    }

    function mint(address to, uint256 amount) public {
        require(owner == msg.sender, "owner");
        _mint(to, amount);
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface IInternalGovernance {
    event VotingPowerGiven(address[] recipients);
    event VotingPowerRemoved(address[] recipients);

    function giveVotingPower(address[] memory _recipients) external;

    function removeVotingPower(address[] memory _recipients) external;
}

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

pragma solidity ^0.8.0;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
 * _Available since v4.9 for `string`, `bytes`._
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }
}

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

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);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface IDynamicJobs {
    event JobExecuted(bytes32 jobHash, address executor);
    event JobRegistered(
        bytes[] jobInfo,
        address[] targetAddresses,
        bytes32 jobHash,
        string name,
        string ipfsForJobDetails
    );
    event JobToggledByCreator(bytes32 jobHash, uint256 toggle);

    function registerJob(
        bytes[] calldata _userProvidedData,
        address[] calldata _targetAddresses,
        string calldata _name,
        string calldata _ipfsForJobDetails
    ) external;

    function registerJobAndDepositGas(
        bytes[] calldata _userProvidedData,
        address[] calldata _targetAddresses,
        string calldata _name,
        string calldata _ipfsForJobDetails
    ) external payable;

    function executeJob(
        address[] calldata _targetAddresses,
        bytes[] calldata _userProvidedData,
        bytes[] calldata _strategyProvidedData
    ) external;

    function setJobState(bytes32 _jobHash, uint256 _toggle) external;

    function withdrawGas(uint256 _amount, address payable to) external;
}

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

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 onlyInitializing {
        __Context_init_unchained();
        __ERC20_init_unchained(name_, symbol_);
    }

    function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
        _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;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)

pragma solidity ^0.8.0;

/**
 * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
 * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
 * be specified by overriding the virtual {_implementation} function.
 *
 * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
 * different contract through the {_delegate} function.
 *
 * The success and return data of the delegated call will be returned back to the caller of the proxy.
 */
abstract contract Proxy {
    /**
     * @dev Delegates the current call to `implementation`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _delegate(address implementation) internal virtual {
        assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize())

            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())

            switch result
            // delegatecall returns 0 on error.
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }

    /**
     * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
     * and {_fallback} should delegate.
     */
    function _implementation() internal view virtual returns (address);

    /**
     * @dev Delegates the current call to the address returned by `_implementation()`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _fallback() internal virtual {
        _beforeFallback();
        _delegate(_implementation());
    }

    /**
     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
     * function in the contract matches the call data.
     */
    fallback() external payable virtual {
        _fallback();
    }

    /**
     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
     * is empty.
     */
    receive() external payable virtual {
        _fallback();
    }

    /**
     * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
     * call, or as part of the Solidity `fallback` or `receive` functions.
     *
     * If overridden should call `super._beforeFallback()`.
     */
    function _beforeFallback() internal virtual {}
}

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

import "../SteerToken.sol";

/// @title Steer Protocol Governance Token
/// @author Steer Protocol
/// @dev This token is used within the governance contracts
contract UpgradedSteerToken is SteerToken {
    uint256 public num;

    function setNum(uint256 _num) external {
        num = _num;
    }
}

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

import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "./interfaces/IRewardOrchestrator.sol";
import "./interfaces/IRunnerRewarder.sol";

contract RunnerRewarder is
    IRunnerRewarder,
    Initializable,
    UUPSUpgradeable,
    OwnableUpgradeable
{
    /// @dev ERC20-claimee inclusion root
    bytes32 public merkleRoot;
    address public rewardOrchestrator;
    IERC20Upgradeable public rewardToken; //Token given as the reward

    /// @dev Mapping of cumulative amount claimed by each address.
    mapping(address => uint256) public claimed;

    modifier onlyRewardOrchestrator() {
        require(
            msg.sender == rewardOrchestrator,
            "You are not permitted to call this function"
        );
        _;
    }

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    function initialize(address _rewardTokenAddress) public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
        rewardToken = IERC20Upgradeable(_rewardTokenAddress);
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}

    /// @dev Allows to set the rewardOrchestrator contract.
    /// @dev Can only be called by owner of this contract.
    /// @param _rewardOrchestrator address of the rewardOrchestrator contract.
    function setRewardOrchestrator(
        address _rewardOrchestrator
    ) external onlyOwner {
        require(_rewardOrchestrator != address(0), "Zero address, Invalid!");
        rewardOrchestrator = _rewardOrchestrator;
    }

    /// @dev Allow to change the merkle root hash stored on this contract
    /// @dev rewardOrchestrator is the contract which is only address capable of changing merkle hash in this contract
    /// @param hash is the new merkle root hash.
    function changeMerkleRootHash(
        bytes32 hash
    ) external onlyRewardOrchestrator {
        merkleRoot = hash;
    }

    /// @dev Allows claiming tokens if hash made from address and amount is part of the merkle tree
    /// @param amount of total tokens earned by claimee. Note that this includes previously claimed tokens.
    /// @param proof merkle proof to prove address and amount are in tree
    function claim(uint256 amount, bytes32[] calldata proof) external {
        uint256 claimedTillNow = claimed[msg.sender];
        require(
            amount > claimedTillNow,
            "This amount has already been claimed"
        );

        // Verify merkle proof, or revert if not in tree
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
        bool isValidLeaf = MerkleProofUpgradeable.verify(
            proof,
            merkleRoot,
            leaf
        );
        require(isValidLeaf, "Calculated Merkle Hash is invalid");

        //Set claimed equal to amount, to prevent double-claiming
        claimed[msg.sender] = amount;

        // Emit claim event
        emit Claim(msg.sender, amount - claimedTillNow);

        // Transfer reward tokens to the claimer
        rewardToken.transfer(msg.sender, amount - claimedTillNow);
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import '../IDataStorageOperator.sol';

/// @title Pool state that never changes
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolImmutables {
  /**
   * @notice The contract that stores all the timepoints and can perform actions with them
   * @return The operator address
   */
  function dataStorageOperator() external view returns (address);

  /**
   * @notice The contract that deployed the pool, which must adhere to the IAlgebraFactory interface
   * @return The contract address
   */
  function factory() external view returns (address);

  /**
   * @notice The first of the two tokens of the pool, sorted by address
   * @return The token contract address
   */
  function token0() external view returns (address);

  /**
   * @notice The second of the two tokens of the pool, sorted by address
   * @return The token contract address
   */
  function token1() external view returns (address);

  /**
   * @notice The maximum amount of position liquidity that can use any tick in the range
   * @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
   * also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
   * @return The max amount of liquidity per tick
   */
  function maxLiquidityPerTick() external view returns (uint128);
}

File 85 of 177 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../token/ERC20/extensions/IERC20Metadata.sol";

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

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Trees proofs.
 *
 * The proofs can be generated using the JavaScript library
 * https://github.com/miguelmota/merkletreejs[merkletreejs].
 * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
 *
 * See `test/utils/cryptography/MerkleProof.test.js` for some examples.
 */
library MerkleProofUpgradeable {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf
    ) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 proofElement = proof[i];
            if (computedHash <= proofElement) {
                // Hash(current computed hash + current element of the proof)
                computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
            } else {
                // Hash(current element of the proof + current computed hash)
                computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
            }
        }
        return computedHash;
    }
}

// SPDX-License-Identifier: BUSL-1.1

/*

¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦_¯_¯_¯_¯_¯_¯¦¦¦¦¦
¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦_¯¦¦¦¦¦
¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦_¯¦¦¦¦¦
¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦_¯¦¦¦¦¦
¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦_¯_¯_¯_¯_¯_¯¦¦¦¦¦
¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦_¯¦¦¦¦¦¦¦
¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦_¯¦¦¦¦¦¦¦
¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦_¯¦¦¦¦¦¦¦
¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦¦¦¦¦_¯¦¦¦¦¦¦¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦_¯_¯_¯_¯_¯¦¦¦¦¦_¯¦¦¦¦¦¦_¯_¯_¯¦¦¦
¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦

*/

pragma solidity 0.8.12;

import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ISmartRewardsDistributor } from "./interfaces/ISmartRewardsDistributor.sol";

contract SmartRewardsDistributor is
    ISmartRewardsDistributor,
    Initializable,
    UUPSUpgradeable,
    OwnableUpgradeable
{
    // Library setup
    using CountersUpgradeable for CountersUpgradeable.Counter;
    using SafeERC20 for IERC20;

    /// @dev ERC20-claimee inclusion root
    bytes32 public merkleRoot;

    // the last block the root hash was updated, used for calculating rewards allocated up to this block
    uint256 public lastBlockUpdate;

    address public feeCollectionAddress;

    // default steer fees, taken from 1e4
    /// @dev Fee rates, each multiplied by 10,000
    uint256 public steerFee;

    uint256 internal constant MAX_STEER_FEE = 25_00;
    uint256 internal constant FEE_DIVISOR = 100_00;

    /// @dev the number of blocks after earning rewards that they are considered abandoned, and claimable by Steer 
    uint256 public abandonmentBlockTimeframe;

    /// @dev whitelisted ERC20 tokens approved to be used as rewards, mapping used to store minimum amounts
    mapping(address => uint256) public minimumRewardTokenAmounts;

    /// @dev a mapping of creators who are given fee discounts for campaigns
    mapping(address => uint256) public feeDiscount;

    // Counter to keep track of rewardCampaigns
    CountersUpgradeable.Counter public rewardCampaignIdTracker;
    mapping(uint256 => RewardCampaign) public rewardCampaigns;

    address public orchestratorRelay;

    /// @dev Mapping of cumulative amount claimed by each address for each campaignId.
    /// user - campaignId - amount
    mapping(address => mapping(uint256 => uint256)) public claims;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    function initialize(
        address _orchestratorRelay,
        uint256 _steerFee,
        address _feeCollectionAddress,
        uint256 _abandonmentBlockTimeframe
    ) public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
        require(
            _orchestratorRelay != address(0),
            "_orchestratorRelay address cannot be 0"
        );
        require(
            _feeCollectionAddress != address(0),
            "_feeCollectionAddress address cannot be 0"
        );
        // @note will need to be known on initialization, deploy dynamic jobs contract first, then register
        orchestratorRelay = _orchestratorRelay;
        steerFee = _steerFee > MAX_STEER_FEE ? MAX_STEER_FEE : _steerFee;
        feeCollectionAddress = _feeCollectionAddress;
        abandonmentBlockTimeframe = _abandonmentBlockTimeframe;

        // Set last updated to current block to prevent canceled campaigns not registering
        lastBlockUpdate = block.number;
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}

    /// @dev The orchestrator is routed to call this function to update the root. 
    /// @param newMerkleRoot the hash of all of the cumulative claims
    /// @param blockUpdated the block the hash processes hashes up to, not the block of the update tx
    function orchestratorUpdateRootHash(bytes32 newMerkleRoot, uint blockUpdated) external onlyOrchestrator {
        require(
            blockUpdated < block.number,
            "Block has not passed yet"
        );
        merkleRoot = newMerkleRoot;
        lastBlockUpdate = blockUpdated;

        emit MerkleRootHashUpdated(merkleRoot, lastBlockUpdate);
    }

    /// @dev Allows claiming tokens if hash made from address and amount is part of the merkle tree
    /// @param users the array of users to claim for, msg.sender must have permissions
    /// @param amounts the amounts for each token earned by claimee. Note that this includes previously claimed tokens.
    /// @param campaignIds the campaigns the claims belong to, a way to track token flows per campaign
    /// @param proofs merkle proofs to prove the token addresses and amounts for the user are in tree
    function claim(
        address[] calldata users,
        uint256[] calldata campaignIds,
        uint256[] calldata amounts,
        bytes32[][] calldata proofs
    ) external {
        uint256 arrayLength = users.length;
        require (
            arrayLength != 0 &&
            arrayLength == amounts.length &&
            arrayLength == proofs.length &&
            arrayLength == campaignIds.length,
            "Array mismatch"
        );

        address own = owner();
        bool isOwner = msg.sender == own;

        for (uint256 i; i != arrayLength;) {

            uint256 id = campaignIds[i];
            RewardCampaign memory campaign = rewardCampaigns[id];
            uint256 amount = amounts[i];
            address user = users[i];

            // allow owner to withdraw unhalted paused funds
            if (campaign.paused && !isOwner) {
                unchecked {
                    ++i;
                }
                continue;
            }

            bytes32 leaf = keccak256(abi.encodePacked(user, id, amount));
            bool isValidLeaf = MerkleProofUpgradeable.verify(
                proofs[i],
                merkleRoot,
                leaf
            );
            require(isValidLeaf, "Calculated Merkle Hash is invalid");

            uint256 outbound = amount - claims[user][id];
            if (outbound == 0) {
                unchecked {
                    ++i;
                }
                continue;
            }
            // credit campaign & check amount
            uint256 totalCampaignCredit = campaign.credit + outbound;
            require(getAvailableFundsUpToBlock(campaign, lastBlockUpdate) >= totalCampaignCredit, "Campaign allocation exceeds distribution");
            // add claim to campaignCredit
            rewardCampaigns[id].credit = totalCampaignCredit;//As only one value is updated no need to store the whole struct in storage first
            claims[user][id] = amount;

            if (isOwner) {
                // allow owner to take custody only if rewards abandoned
                require(block.number >= campaign.abandonmentDeadlineBlock, "Custody not transfered yet");
                user = own;
            }

            IERC20(campaign.rewardToken).safeTransfer(user, outbound);
            unchecked {
                ++i;
            }
            emit UserClaim(user, campaign.rewardToken, outbound, id, totalCampaignCredit);
        }
    }

    /// @dev Registers a new reward campaign, fees are taken here based on the steer fee and possibly reduced if the msg.sender has a discount.
    ///     Reward tokens must be whitelisted on the minimumRewardTokenAmounts by having a minimum amount of token to give as rewards
    /// @param newRewardCampaign Struct holding registry data for new campaign
    function createRewardCampaign(
        RewardCampaignParams memory newRewardCampaign
    ) external returns (uint256) {
        uint256 minRewardTokenAmount =  minimumRewardTokenAmounts[newRewardCampaign.rewardToken];
        require(
           minRewardTokenAmount != 0,
            "Invalid Reward Token"
        );
        require(
            minRewardTokenAmount <= newRewardCampaign.escrowAmount,
            "Insufficient amount for token"
        );
        require(
            newRewardCampaign.startBlock <
                newRewardCampaign.endBlock,
            "Start of campaign must be before end"
        );
        require(
            newRewardCampaign.startBlock > block.number,
            "Start of campaign must be in the future"
        );
        require(
            isIPFS(newRewardCampaign.campaignParamsIPFSHash),
            "Bundle must be an IPFS CID hash"
        );

        uint256 discount = feeDiscount[msg.sender];
        uint256 distributionRewards;
        uint256 campaignFee;
        unchecked {
            campaignFee = steerFee >= discount ? steerFee - discount : 0;
        }
        if (campaignFee != 0) {
            uint256 rewardSteerFees = (newRewardCampaign.escrowAmount * campaignFee) /
                FEE_DIVISOR;
            distributionRewards = newRewardCampaign.escrowAmount -
                rewardSteerFees;
            IERC20(newRewardCampaign.rewardToken).safeTransferFrom(
                msg.sender,
                feeCollectionAddress,
                rewardSteerFees
            );
        }
        else {
            distributionRewards = newRewardCampaign.escrowAmount;
        }

        IERC20(newRewardCampaign.rewardToken).safeTransferFrom(
            msg.sender,
            address(this),
            distributionRewards
        );

        uint256 campaignId = rewardCampaignIdTracker.current();
        rewardCampaignIdTracker.increment();
        if (newRewardCampaign.owner == address(0))
            newRewardCampaign.owner = msg.sender;
        uint256 abandonDeadline = newRewardCampaign.endBlock + abandonmentBlockTimeframe;
        rewardCampaigns[campaignId] = RewardCampaign({
            owner: newRewardCampaign.owner,
            liquidityPool: newRewardCampaign.liquidityPool,
            rewardToken: newRewardCampaign.rewardToken,
            amount: distributionRewards,
            startBlock: newRewardCampaign.startBlock,
            endBlock: newRewardCampaign.endBlock,
            campaignParamsIPFSHash: newRewardCampaign.campaignParamsIPFSHash,
            abandonmentDeadlineBlock: abandonDeadline,
            cancelBlock: 0,
            paused: false,
            credit: 0,
            halted: false
        });
        emit RewardCampaignCreated(
            campaignId,
            newRewardCampaign.owner,
            newRewardCampaign.startBlock,
            newRewardCampaign.endBlock,
            newRewardCampaign.liquidityPool,
            newRewardCampaign.rewardToken,
            distributionRewards,
            newRewardCampaign.campaignParamsIPFSHash,
            abandonDeadline
        );

        return campaignId;
    }

    // @steer can be automated
    function setFeeDiscount(
        address campaignCreator,
        uint256 discount
    ) external onlyOwner {
        feeDiscount[campaignCreator] = discount;
        emit FeeDiscountChanged(campaignCreator, discount);
    }

    /// @dev Set the number of blocks after a campaign ends to where the reward tokens are considered abandoned,
    /// updating the timeframe will only apply to new campaigns moving forward
    /// @param newBlockTimeframe number of blocks before unclaimed reward tokens are abandoned
    function setAbandonmentBlockTimeframe(uint256 newBlockTimeframe) external onlyOwner {
        abandonmentBlockTimeframe = newBlockTimeframe;
        emit AbandonmentBlockTimeframeChanged(newBlockTimeframe);
    }


    /// @dev cancel a campaign from distributing rewards, refund tokens to owner
    /// @param campaignId the id of the campaign to cancel
    /// @param halt true if all outstanding rewards should also be refunded
    function cancelCampaign(uint256 campaignId, bool halt) external returns (uint256) {

        RewardCampaign memory campaign = rewardCampaigns[campaignId];//for reading data
        RewardCampaign storage _campaign = rewardCampaigns[campaignId];//for writing data
        require(msg.sender == campaign.owner, "Must be owner to cancel");

        uint lastBlockIncluded = lastBlockUpdate;
        require(campaign.endBlock > lastBlockIncluded, "Campaign must be active or pending");
        require(campaign.cancelBlock  == 0, "Campaign already canceled");

        uint256 campaignSpent = campaign.credit;
        uint256 outbound;
        // pause all withdraws
        if (halt) {
            // ALL tokens on hand refunded
            outbound = campaign.amount - campaignSpent;
            _campaign.credit = campaign.amount;
            IERC20(campaign.rewardToken).safeTransfer(
                campaign.owner,
                outbound
            );
            _campaign.paused = true;
            _campaign.halted = true;
            emit CampaignPaused(campaignId, msg.sender, true);
        }
        else {
            // Only tokens not allocated refunded
            uint256 totalAllocated = getAvailableFundsUpToBlock(campaign, lastBlockIncluded);
            outbound = campaign.amount - totalAllocated;
            _campaign.credit = campaignSpent + outbound;
            IERC20(campaign.rewardToken).safeTransfer(
                campaign.owner,
                outbound
            );
        }
        // mark campaign as canceled
        _campaign.cancelBlock = lastBlockUpdate;

        emit CampaignCanceled(campaignId, halt, lastBlockIncluded, outbound, msg.sender);
        return outbound;
    }

    /// @dev acts as a whitelist, if the min amount is not set then we have not whitelisted
    function setMinimumRewardToken(
        address token,
        uint256 minAmount
    ) external onlyOwner {
        minimumRewardTokenAmounts[token] = minAmount;
        emit RewardTokenChanged(token, minAmount);
    }

    function transferCampaignOwnership(
        uint256 campaignId,
        address newOwner
    ) external {
        require(newOwner != address(0), "Owner cannot be zero address");
        RewardCampaign storage campaign = rewardCampaigns[campaignId];
        require(
            msg.sender == campaign.owner,
            "Must be owner to transfer ownership"
        );
        campaign.owner = newOwner;
        emit CampaignOwnershipTransfer(campaignId, newOwner);
    }

    function setSteerFee(uint256 newSteerFee) external onlyOwner {
        if (newSteerFee > MAX_STEER_FEE) newSteerFee = MAX_STEER_FEE;
        steerFee = newSteerFee;
        emit SteerFeesChanged(newSteerFee);
    }

    /// @dev Check for a distribution up to a block how many rewards total have been emitted
    function getAvailableFundsUpToBlock(RewardCampaign memory campaign, uint256 lastBlock) public pure returns (uint256) {
        if (campaign.cancelBlock != 0) return campaign.amount;
        if (campaign.startBlock > lastBlock) return 0;
        uint256 blockDelta;
        uint256 blockProgress;
        unchecked {
            blockDelta = campaign.endBlock - campaign.startBlock;
            blockProgress = (lastBlock > campaign.endBlock ? campaign.endBlock : lastBlock) - campaign.startBlock;
        }
        return campaign.amount * blockProgress / blockDelta;
    }

    function setFeeCollectionAddress(address newFeeCollectionAddress) external onlyOwner {
        require(
            newFeeCollectionAddress != address(0),
            "newFeeCollectionAddress address cannot be 0"
        );
        feeCollectionAddress = newFeeCollectionAddress;
        emit FeeCollectionAddressChanged(newFeeCollectionAddress);
    }

    /// @dev for emergencies or misRegisters, cancel campaign should be used in most circumstances
    function toggleCampaignPause(uint campaignId) external returns (bool) {
        RewardCampaign memory campaign = rewardCampaigns[campaignId];
        require(msg.sender == campaign.owner || msg.sender == owner(), "Must be campaign owner to pause");

        bool state = !campaign.paused;
        require (campaign.halted == false, "Cannot unpause halted campaign"); // No need to check for if halted cancelled campaign is paused or not 
        // as cancelling the camaign automatically pauses the campaign
        rewardCampaigns[campaignId].paused = state;//As only one value is updated no need to store the whole struct in storage first
        emit CampaignPaused(campaignId, msg.sender, state);
        return state;
    }

    modifier onlyOrchestrator() {
        // This function reverts if msg.sender is not a keeper
        require(msg.sender == orchestratorRelay, "Orchestrator call via dynamic vault only");
        _;
    }

    /// @dev Checks if the passed string is a IPFS link or not. @NOTE UPGRADED to remove logic
    /// @param source String that needs to checked.
    /// @return true if the string passed is IPFS, else it will return false.
    function isIPFS(string memory source) internal pure returns (bool) {
        // bytes memory sourceToBytes = bytes(source);
        return (true);
    }
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract QuickSwapIntegralBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0x8Ff309F68F6Caf77a78E9C20d2Af7Ed4bE2D7093);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

File 89 of 177 : IUniswapV3PoolEvents.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Events emitted by a pool
/// @notice Contains all events emitted by the pool
interface IUniswapV3PoolEvents {
    /// @notice Emitted exactly once by a pool when #initialize is first called on the pool
    /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize
    /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96
    /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
    event Initialize(uint160 sqrtPriceX96, int24 tick);

    /// @notice Emitted when liquidity is minted for a given position
    /// @param sender The address that minted the liquidity
    /// @param owner The owner of the position and recipient of any minted liquidity
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity minted to the position range
    /// @param amount0 How much token0 was required for the minted liquidity
    /// @param amount1 How much token1 was required for the minted liquidity
    event Mint(
        address sender,
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted when fees are collected by the owner of a position
    /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
    /// @param owner The owner of the position for which fees are collected
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount0 The amount of token0 fees collected
    /// @param amount1 The amount of token1 fees collected
    event Collect(
        address indexed owner,
        address recipient,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount0,
        uint128 amount1
    );

    /// @notice Emitted when a position's liquidity is removed
    /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
    /// @param owner The owner of the position for which liquidity is removed
    /// @param tickLower The lower tick of the position
    /// @param tickUpper The upper tick of the position
    /// @param amount The amount of liquidity to remove
    /// @param amount0 The amount of token0 withdrawn
    /// @param amount1 The amount of token1 withdrawn
    event Burn(
        address indexed owner,
        int24 indexed tickLower,
        int24 indexed tickUpper,
        uint128 amount,
        uint256 amount0,
        uint256 amount1
    );

    /// @notice Emitted by the pool for any swaps between token0 and token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the output of the swap
    /// @param amount0 The delta of the token0 balance of the pool
    /// @param amount1 The delta of the token1 balance of the pool
    /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
    /// @param liquidity The liquidity of the pool after the swap
    /// @param tick The log base 1.0001 of price of the pool after the swap
    event Swap(
        address indexed sender,
        address indexed recipient,
        int256 amount0,
        int256 amount1,
        uint160 sqrtPriceX96,
        uint128 liquidity,
        int24 tick
    );

    /// @notice Emitted by the pool for any flashes of token0/token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the tokens from flash
    /// @param amount0 The amount of token0 that was flashed
    /// @param amount1 The amount of token1 that was flashed
    /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
    /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
    event Flash(
        address indexed sender,
        address indexed recipient,
        uint256 amount0,
        uint256 amount1,
        uint256 paid0,
        uint256 paid1
    );

    /// @notice Emitted by the pool for increases to the number of observations that can be stored
    /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index
    /// just before a mint/swap/burn.
    /// @param observationCardinalityNextOld The previous value of the next observation cardinality
    /// @param observationCardinalityNextNew The updated value of the next observation cardinality
    event IncreaseObservationCardinalityNext(
        uint16 observationCardinalityNextOld,
        uint16 observationCardinalityNextNew
    );

    /// @notice Emitted when the protocol fee is changed by the pool
    /// @param feeProtocol0Old The previous value of the token0 protocol fee
    /// @param feeProtocol1Old The previous value of the token1 protocol fee
    /// @param feeProtocol0New The updated value of the token0 protocol fee
    /// @param feeProtocol1New The updated value of the token1 protocol fee
    event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);

    /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner
    /// @param sender The address that collects the protocol fees
    /// @param recipient The address that receives the collected protocol fees
    /// @param amount0 The amount of token0 protocol fees that is withdrawn
    /// @param amount0 The amount of token1 protocol fees that is withdrawn
    event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);
}

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

interface IRunnerRegistry {
    /// @dev MUST be triggered when the runner amount is changed.
    event RunnerAmountChanged(address runner, uint256 amount);

    function bond(uint256 _amount) external;

    function bondAmount() external view returns (uint256);

    function bondToken() external view returns (address);

    function isRunner(address _runner) external view returns (bool);

    function keeperRegistry() external view returns (address);

    function runners(address) external view returns (uint256);

    function slash(address _runner, uint256 _amount) external;

    function unbond(uint256 _amount) external;

    function withdrawFreeCoin(uint256 amount) external;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol";

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

import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";

import { ISteerPeriphery } from "./interfaces/ISteerPeriphery.sol";
import { IGasVault } from "./interfaces/IGasVault.sol";
import { IStrategyRegistry } from "./interfaces/IStrategyRegistry.sol";
import { IVaultRegistry } from "./interfaces/IVaultRegistry.sol";
import { IStakingRewards } from "./interfaces/IStakingRewards.sol";
import { IMultiPositionManager } from "./interfaces/IMultiPositionManager.sol";
import { IBaseDeposit } from "./interfaces/IBaseDeposit.sol";
import { IDynamicJobs } from "./interfaces/IDynamicJobs.sol";

/// @title Periphery contract to facilitate common actions on the protocol
/// @author Steer Protocol
/// @dev You can use this contract to enumerate strategy and vault details but also create, join, and leave vaults
/// @dev This contract is not intended to hold any tokens in between two or more transactions, it only hold tokens transiently within a single transaction and transfers the tokens in the same transaction.
/// @dev This function should be used when doing protocol integrations
contract SteerPeriphery is
    ISteerPeriphery,
    Initializable,
    OwnableUpgradeable,
    UUPSUpgradeable
{
    using ERC165CheckerUpgradeable for address;
    using SafeERC20 for IERC20;
    // Storage

    address internal strategyRegistry;
    address internal vaultRegistry;
    address internal stakingRewards;
    address internal gasVault;

    /// @notice The IPFS reference of the node configuration.
    string public nodeConfig;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    // External Functions

    function initialize(
        address _strategyRegistry,
        address _vaultRegistry,
        address _gasVault,
        address _stakingRewards,
        string calldata _nodeConfig
    ) external initializer {
        __UUPSUpgradeable_init();
        __Ownable_init();
        strategyRegistry = _strategyRegistry;
        vaultRegistry = _vaultRegistry;
        gasVault = _gasVault;
        stakingRewards = _stakingRewards;
        nodeConfig = _nodeConfig;
    }

    ///@dev Only for owner
    ///@dev Sets the config for node
    function setNodeConfig(string memory _nodeConfig) external onlyOwner {
        nodeConfig = _nodeConfig;
    }

    /// @dev Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Uniswap until the next rebalance.
    /// @param vaultAddress The address of the vault to deposit to
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting `amount0` is less than this
    /// @param amount1Min Revert if resulting `amount1` is less than this
    /// @param to Recipient of shares
    function deposit(
        address vaultAddress,
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external {
        _deposit(
            vaultAddress,
            amount0Desired,
            amount1Desired,
            amount0Min,
            amount1Min,
            to
        );
    }

    /// @dev Deposits tokens in proportion to the vault's current holdings and stake the share.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Uniswap until the next rebalance.
    /// @param vaultAddress The address of the vault to deposit to
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting `amount0` is less than this
    /// @param amount1Min Revert if resulting `amount1` is less than this
    /// @param poolId The id of the pool in which the share should be staked
    function depositAndStake(
        address vaultAddress,
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        uint256 poolId
    ) external {
        require(
            vaultAddress != address(0) &&
                IStakingRewards(stakingRewards).getPool(poolId).stakingToken ==
                vaultAddress,
            "Incorrect pool id"
        );

        uint256 share = _deposit(
            vaultAddress,
            amount0Desired,
            amount1Desired,
            amount0Min,
            amount1Min,
            address(this)
        );

        IERC20(vaultAddress).approve(stakingRewards, share);
        IStakingRewards(stakingRewards).stakeFor(msg.sender, share, poolId);
    }

    function vaultDetailsByAddress(
        address vault
    )
        external
        view
        returns (IMultiPositionManager.VaultDetails memory details)
    {
        IMultiPositionManager vaultInstance = IMultiPositionManager(vault);

        IERC20Metadata token0 = IERC20Metadata(vaultInstance.token0());
        IERC20Metadata token1 = IERC20Metadata(vaultInstance.token1());

        // Get total amounts, excluding fees
        (uint256 bal0, uint256 bal1) = vaultInstance.getTotalAmounts();

        return
            IMultiPositionManager.VaultDetails(
                IVaultRegistry(vaultRegistry).beaconTypes(vault),
                vaultInstance.token0(),
                vaultInstance.token1(),
                IERC20Metadata(vault).name(),
                IERC20Metadata(vault).symbol(),
                IERC20Metadata(vault).decimals(),
                token0.name(),
                token1.name(),
                token0.symbol(),
                token1.symbol(),
                token0.decimals(),
                token1.decimals(),
                IUniswapV3Pool(vaultInstance.pool()).fee(),
                vaultInstance.totalSupply(),
                bal0,
                bal1,
                vault
            );
    }

    function algebraVaultDetailsByAddress(
        address vault
    )
        external
        view
        returns (IMultiPositionManager.AlgebraVaultDetails memory details)
    {
        IMultiPositionManager vaultInstance = IMultiPositionManager(vault);

        IERC20Metadata token0 = IERC20Metadata(vaultInstance.token0());
        IERC20Metadata token1 = IERC20Metadata(vaultInstance.token1());

        // Get total amounts, excluding fees
        (uint256 bal0, uint256 bal1) = vaultInstance.getTotalAmounts();

        return
            IMultiPositionManager.AlgebraVaultDetails(
                IVaultRegistry(vaultRegistry).beaconTypes(vault),
                vaultInstance.token0(),
                vaultInstance.token1(),
                IERC20Metadata(vault).name(),
                IERC20Metadata(vault).symbol(),
                IERC20Metadata(vault).decimals(),
                token0.name(),
                token1.name(),
                token0.symbol(),
                token1.symbol(),
                token0.decimals(),
                token1.decimals(),
                vaultInstance.totalSupply(),
                bal0,
                bal1,
                vault
            );
    }

    function vaultBalancesByAddressWithFees(
        address vault
    ) external returns (IMultiPositionManager.VaultBalance memory balances) {
        IMultiPositionManager vaultInstance = IMultiPositionManager(vault);
        vaultInstance.poke();
        (uint256 bal0, uint256 bal1) = vaultInstance.getTotalAmounts();

        return IMultiPositionManager.VaultBalance(bal0, bal1);
    }

    function createVaultAndStrategy(
        CVSParams calldata cvsParams
    ) external payable returns (uint256 tokenId, address newVault) {
        tokenId = IStrategyRegistry(strategyRegistry).createStrategy(
            cvsParams.strategyCreator,
            cvsParams.name,
            cvsParams.execBundle,
            cvsParams.maxGasCost,
            cvsParams.maxGasPerAction
        );
        newVault = IVaultRegistry(vaultRegistry).createVault(
            cvsParams.params,
            tokenId,
            cvsParams.beaconName,
            cvsParams.vaultManager,
            cvsParams.payloadIpfs
        );
        // Deposit gas
        IGasVault(gasVault).deposit{ value: msg.value }(newVault);
    }

    function createVaultAndDepositGas(
        CVDGParams calldata cvdgParams
    ) external payable returns (address newVault) {
        newVault = IVaultRegistry(vaultRegistry).createVault(
            cvdgParams.params,
            cvdgParams.tokenId,
            cvdgParams.beaconName,
            cvdgParams.vaultManager,
            cvdgParams.payloadIpfs
        );
        // Deposit gas
        IGasVault(gasVault).deposit{ value: msg.value }(newVault);
    }

    function createVaultStrategyAndRegisterJob(
        CVSRJParams calldata cvsrjParams
    ) external payable returns (uint256 tokenId, address newVault) {
        tokenId = IStrategyRegistry(strategyRegistry).createStrategy(
            cvsrjParams.strategyCreator,
            cvsrjParams.name,
            cvsrjParams.execBundle,
            cvsrjParams.maxGasCost,
            cvsrjParams.maxGasPerAction
        );
        newVault = IVaultRegistry(vaultRegistry).createVault(
            cvsrjParams.jobInitParams,
            tokenId,
            cvsrjParams.beaconName,
            cvsrjParams.vaultManager,
            cvsrjParams.payloadIpfs
        );
        // Deposit gas
        IGasVault(gasVault).deposit{ value: msg.value }(newVault);

        //Regitser Job
        IDynamicJobs(newVault).registerJob(
            cvsrjParams.userProvidedData,
            cvsrjParams.targetAddresses,
            cvsrjParams.jobName,
            cvsrjParams.ipfsForJobDetails
        );
    }

    // Public Functions

    /// @dev Get the strategies by creator
    /// @param creator The creator of the strategies
    /// @return The List of strategies created by the creator
    function strategiesByCreator(
        address creator
    ) public view returns (IStrategyRegistry.RegisteredStrategy[] memory) {
        // Find the userse balance
        uint256 strategyBalance = IStrategyRegistry(strategyRegistry)
            .balanceOf(creator);

        // Create an array to hold the strategy details
        // which is the same length of the balance of the registered strategies
        IStrategyRegistry.RegisteredStrategy[]
            memory userStrategies = new IStrategyRegistry.RegisteredStrategy[](
                strategyBalance
            );

        uint256 tokenId;
        // Iterate through the user's strategies and fill the array
        for (uint256 i; i != strategyBalance; ++i) {
            // Get token id of a strategy based on the owner and the index of the strategy
            tokenId = IStrategyRegistry(strategyRegistry).tokenOfOwnerByIndex(
                creator,
                i
            );

            // Using the tokenId, get the strategy details from the strategy registry
            IStrategyRegistry.RegisteredStrategy
                memory strategy = IStrategyRegistry(strategyRegistry)
                    .getRegisteredStrategy(tokenId);

            // Add the strategy to the array based on the loop index
            userStrategies[i] = strategy;
        }

        // Return the array of strategies
        return userStrategies;
    }

    /// @dev Get the vaults using a given strategy
    /// @param strategyId The strategyId (ERC-721 tokenId)
    /// @return The List of vault details using the strategy
    function vaultsByStrategy(
        uint256 strategyId
    ) public view returns (IVaultRegistry.VaultData[] memory) {
        // Get the amount of vaults using the strategy
        uint256 vaultCount = IVaultRegistry(vaultRegistry)
            .getVaultCountByStrategyId(strategyId);

        // Create an array to hold the vault details
        IVaultRegistry.VaultData[]
            memory strategyVaults = new IVaultRegistry.VaultData[](vaultCount);

        IVaultRegistry.VaultData memory thisData;
        // Iterate through the vaults and fill the array
        for (uint256 i; i != vaultCount; ++i) {
            // Retrieve the VaultData struct using the
            // strategyId and the index of the vault for the strategy
            thisData = IVaultRegistry(vaultRegistry)
                .getVaultByStrategyAndIndex(strategyId, i);

            // Add the vault to the array based on the loop index
            strategyVaults[i] = thisData;
        }

        // Return the array of vaultData
        return strategyVaults;
    }

    // Internal Functions

    /// @dev Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Uniswap until the next rebalance.
    /// @param vaultAddress The address of the vault to deposit to
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting `amount0` is less than this
    /// @param amount1Min Revert if resulting `amount1` is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    function _deposit(
        address vaultAddress,
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) internal returns (uint256) {
        require(
            keccak256(
                abi.encodePacked(
                    IVaultRegistry(vaultRegistry).beaconTypes(vaultAddress)
                )
            ) != keccak256(abi.encodePacked("")),
            "Invalid Vault"
        );
        IMultiPositionManager vaultInstance = IMultiPositionManager(
            vaultAddress
        );
        IERC20 token0 = IERC20(vaultInstance.token0());
        IERC20 token1 = IERC20(vaultInstance.token1());
        if (amount0Desired > 0)
            token0.safeTransferFrom(msg.sender, address(this), amount0Desired);
        if (amount1Desired > 0)
            token1.safeTransferFrom(msg.sender, address(this), amount1Desired);
        token0.approve(vaultAddress, amount0Desired);
        token1.approve(vaultAddress, amount1Desired);

        (uint256 share, uint256 amount0, uint256 amount1) = vaultInstance
            .deposit(
                amount0Desired,
                amount1Desired,
                amount0Min,
                amount1Min,
                to
            );

        if (amount0Desired > amount0) {
            token0.approve(vaultAddress, 0);
            token0.safeTransfer(msg.sender, amount0Desired - amount0);
        }
        if (amount1Desired > amount1) {
            token1.approve(vaultAddress, 0);
            token1.safeTransfer(msg.sender, amount1Desired - amount1);
        }

        return share;
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}
}

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

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";

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

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

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

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

pragma solidity ^0.8.0;

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

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

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

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

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ArraysUpgradeable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { IKeeperRegistry } from "./interfaces/IKeeperRegistry.sol";

/**
 * @dev owned by governance contract
 */
contract KeeperRegistry is
    Initializable,
    OwnableUpgradeable,
    UUPSUpgradeable,
    IKeeperRegistry
{
    using SafeERC20 for IERC20;
    uint256 public maxNumKeepers; // Max # of keepers to allow at a time
    uint256 public currentNumKeepers; // Current # of keepers.

    // Bond token
    IERC20 public bondCoin; // ERC20 token used to provide bonds. Meant to be Steer token.
    uint256 public bondAmount; // Amount of bondCoin required to become a keeper
    uint256 public freeCoin; // Amount of bondCoin no longer affiliated with any keeper (due to slashing etc.)

    /**
     * Slash safety period--if a keeper leaves, this is the amount of time (in seconds) they must 
        wait before they can withdraw their bond.
     */
    uint256 public transferDelay;

    mapping(uint256 => address) public keeperLicenses; // This mapping is pretty much just used to track which licenses are free.
    mapping(address => WorkerDetails) public registry; // Registry of keeper info for keepers and former keepers.

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    function initialize(
        address coinAddress,
        uint256 keeperTransferDelay,
        uint256 maxKeepers,
        uint256 bondSize
    ) public initializer {
        __UUPSUpgradeable_init();
        __Ownable_init();
        bondCoin = IERC20(coinAddress);
        transferDelay = keeperTransferDelay;
        maxNumKeepers = maxKeepers;
        require(bondSize > 0, "SIZE");
        bondAmount = bondSize;
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}

    /**
     * @dev setup utility function for owner to add initial keepers. Addresses must each be unique and not hold any bondToken.
     * @param joiners array of addresses to become keepers.
     * note that this function will pull bondToken from the owner equal to bondAmount * numJoiners.
     * note that this function assumes that the keeper registry currently has no keepers. It will revert if this assumption fails.
     */
    function joiningForOwner(address[] calldata joiners) public onlyOwner {
        uint256 noOfJoiners = joiners.length;
        require(noOfJoiners != 0, "JOINERS");
        // Cache last license index
        uint256 lastKeeperLicense = noOfJoiners + 1;

        // Cache bond amount
        uint256 _bondAmount = bondAmount;

        bondCoin.safeTransferFrom(
            msg.sender,
            address(this),
            _bondAmount * noOfJoiners
        );

        // Ensure not too many keepers are being added.
        require(noOfJoiners <= maxNumKeepers, "MAX_KEEPERS");

        // Add each keeper to the registry
        for (uint256 i = 1; i != lastKeeperLicense; ++i) {
            // Make sure license is not already claimed by another keeper
            require(keeperLicenses[i] == address(0), "Address not new.");

            // Register keeper to license
            keeperLicenses[i] = joiners[i - 1];

            // Register license (and other info) to keeper
            registry[joiners[i - 1]] = WorkerDetails({
                bondHeld: _bondAmount,
                licenseNumber: i,
                leaveTimestamp: 0
            });
        }

        currentNumKeepers += noOfJoiners;
    }

    /**
     * @param amount Amount of bondCoin to be deposited.
     * @dev this function has three uses:
        1. If the caller is a keeper, they can increase their bondHeld by amount. This helps to avoid slashing.
        2. If the caller is not a keeper or former keeper, they can attempt to claim a keeper license and become a keeper.
        3. If the caller is a former keeper, they can attempt to cancel their leave request, claim a keeper license, and become a keeper.
        In all 3 cases registry[msg.sender].bondHeld is increased by amount. In the latter 2, msg.sender's bondHeld after the transaction must be >= bondAmount.
     */
    function join(uint256 licenseNumber, uint256 amount) public {
        // Transfer in bond.
        if (amount > 0) {
            bondCoin.safeTransferFrom(msg.sender, address(this), amount);
        }

        // Look up msg.sender in the mapping
        WorkerDetails memory _workerDetails = registry[msg.sender];

        if (_workerDetails.licenseNumber > 0) {
            // If they have a license, they're a keeper, and amount will go towards their bondHeld
            // If maxNumKeepers was decreased, they may not be a keeper, but this won't cause anything to break.
            registry[msg.sender].bondHeld = _workerDetails.bondHeld + amount;
        } else {
            /*
                Two scenarios here:
                1. If their bondAmount is zero and their leaveTimestamp is zero, they are not yet a keeper, so this is a new address attempting to become a keeper.
                2. If they are queued to leave but have not yet left, they are not a keeper, so this will cancel their leave request (by zeroing out leaveTimestamp) 
                and attempt to make them a keeper.
                Either way the solution is the same -- if their new bondAmount is enough, they become a keeper with no leave date. Otherwise, this function reverts.
            */

            // Make sure requested license is valid and available
            require(
                keeperLicenses[licenseNumber] == address(0),
                "License not available."
            );
            require(licenseNumber > 0, "LICENSE_NUMBER");
            require(licenseNumber <= maxNumKeepers, "LICENSE_NUMBER");

            // Join must be sufficient to become a keeper
            uint256 newBondAmount = _workerDetails.bondHeld + amount;
            require(newBondAmount >= bondAmount, "Insufficient bond amount.");

            ++currentNumKeepers;

            // Register license/bond amount with keeper
            registry[msg.sender] = WorkerDetails({
                bondHeld: newBondAmount,
                licenseNumber: licenseNumber,
                leaveTimestamp: 0
            });

            // Register keeper with license
            keeperLicenses[licenseNumber] = msg.sender;

            emit PermissionChanged(msg.sender, permissionType.FULL);
        }
    }

    /**
     * @dev Allows keepers to queue to leave the registry. Their elevated permissions are immediately revoked, and their funds can be retrieved once the transferDelay has passed.
     * note that this function can only be called by keepers (or, in rare cases, former keepers whose licenses were revoked by a decrease in maxNumKeepers)
     * emits a permissionChanged event if the call succeeds.
     */
    function queueToLeave() public {
        WorkerDetails memory _workerDetails = registry[msg.sender];
        require(
            _workerDetails.licenseNumber > 0,
            "msg.sender is already not a keeper."
        );

        // Remove permissions immediately. Keeper can remove funds once the transferDelay has passed. This ensures that keeper can be slashed if they misbehaved just before leaving.
        registry[msg.sender] = WorkerDetails({
            bondHeld: _workerDetails.bondHeld,
            licenseNumber: 0,
            leaveTimestamp: block.timestamp + transferDelay
        });
        delete keeperLicenses[_workerDetails.licenseNumber];

        // Decrease numKeepers count
        --currentNumKeepers;

        emit PermissionChanged(msg.sender, permissionType.NONE);
    }

    /**
     * @dev addresses call this after they have queued to leave and waited the requisite amount of time.
     */
    function leave() external {
        WorkerDetails memory info = registry[msg.sender];

        // Validate leave request
        require(info.leaveTimestamp > 0, "Not queued to leave.");
        require(
            info.leaveTimestamp < block.timestamp,
            "Transfer delay not passed."
        );

        // Send former keeper their previously staked tokens
        bondCoin.safeTransfer(msg.sender, info.bondHeld);

        // Reset the former keeper's data
        delete registry[msg.sender];
    }

    /**
     * @dev returns true if the given address has the power to vote, reverts otherwise. This function is built to be called by the orchestrator.
     * @param targetAddress address to check
     * @return licenseNumber true if the given address has the power to vote, reverts otherwise.
     */
    function checkLicense(
        address targetAddress
    ) public view returns (uint256 licenseNumber) {
        licenseNumber = registry[targetAddress].licenseNumber;
        require(licenseNumber > 0, "NOT_A_KEEPER");
    }

    /**
     * @dev slashes a keeper, removing their permissions and forfeiting their bond.
     * @param targetKeeper keeper to denounce
     * @param amount amount of bondCoin to slash
     * note that the keeper will only lose their license if, post-slash, their bond held is less than bondAmount.
     */
    function denounce(
        address targetKeeper,
        uint256 amount
    ) external onlyOwner {
        WorkerDetails memory _workerDetails = registry[targetKeeper];

        // Remove bondCoin from keeper who is being denounced, add to freeCoin (to be withdrawn by owner)
        uint256 currentBondHeld = _workerDetails.bondHeld;

        // If slash amount is greater than keeper's held bond, just slash 100% of their bond
        if (currentBondHeld < amount) {
            amount = currentBondHeld;
        }

        // Slash keeper's bond by amount
        uint256 newBond = currentBondHeld - amount;
        // Add keeper's slashed bond tokens to freeCoin
        freeCoin += amount;

        // Remove user as keeper if they are below threshold, and are a keeper
        if (newBond < bondAmount && _workerDetails.licenseNumber > 0) {
            keeperLicenses[_workerDetails.licenseNumber] = address(0);
            registry[targetKeeper].licenseNumber = 0;
            --currentNumKeepers;
            registry[targetKeeper].bondHeld = 0;
            //User is not a keeper anymore so user's remaining bond amount after substracting the slashed amount is given back
            bondCoin.safeTransfer(targetKeeper, newBond);
            //User can again try to become a keeper by calling join and bonding again.
        } else {
            registry[targetKeeper].bondHeld = newBond;
        }
        emit PermissionChanged(targetKeeper, permissionType.SLASHED);
    }

    /**
     * @dev withdraws slashed tokens from the vault and sends them to targetAddress.
     * @param amount amount of bondCoin to withdraw
     * @param targetAddress address receiving the tokens
     */
    function withdrawFreeCoin(
        uint256 amount,
        address targetAddress
    ) external onlyOwner {
        freeCoin -= amount;
        bondCoin.safeTransfer(targetAddress, amount);
    }

    /**
     * @dev change bondAmount to a new value.
     * @dev Does not change existing keeper permissions. If the bondAmount is being increased, existing keepers will not be slashed or removed. 
            note, they will still be able to vote until they are slashed.
     * @param amount new bondAmount.
     */
    function changeBondAmount(uint256 amount) external onlyOwner {
        bondAmount = amount;
    }

    /**
     * @dev change numKeepers to a new value. If numKeepers is being reduced, this will not remove any keepers, nor will it change orchestrator requirements.
        However, it will render keeper licenses > maxNumKeepers invalid and their votes will stop counting.
     */
    function changeMaxKeepers(uint16 newNumKeepers) external onlyOwner {
        maxNumKeepers = newNumKeepers;
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that is not stored
/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the
/// blockchain. The functions here may have variable gas costs.
interface IUniswapV3PoolDerivedState {
    /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp
    /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing
    /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick,
    /// you must call it with secondsAgos = [3600, 0].
    /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in
    /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.
    /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned
    /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp
    /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block
    /// timestamp
    function observe(uint32[] calldata secondsAgos)
        external
        view
        returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);

    /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range
    /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed.
    /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first
    /// snapshot is taken and the second snapshot is taken.
    /// @param tickLower The lower tick of the range
    /// @param tickUpper The upper tick of the range
    /// @return tickCumulativeInside The snapshot of the tick accumulator for the range
    /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range
    /// @return secondsInside The snapshot of seconds per liquidity for the range
    function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
        external
        view
        returns (
            int56 tickCumulativeInside,
            uint160 secondsPerLiquidityInsideX128,
            uint32 secondsInside
        );
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that can change
/// @dev Important security note: when using this data by external contracts, it is necessary to take into account the possibility
/// of manipulation (including read-only reentrancy).
/// This interface is based on the UniswapV3 interface, credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolState {
  /// @notice Safely get most important state values of Algebra Integral AMM
  /// @dev Several values exposed as a single method to save gas when accessed externally.
  /// **Important security note: this method checks reentrancy lock and should be preferred in most cases**.
  /// @return sqrtPrice The current price of the pool as a sqrt(dToken1/dToken0) Q64.96 value
  /// @return tick The current global tick of the pool. May not always be equal to SqrtTickMath.getTickAtSqrtRatio(price) if the price is on a tick boundary
  /// @return lastFee The current (last known) pool fee value in hundredths of a bip, i.e. 1e-6 (so '100' is '0.01%'). May be obsolete if using dynamic fee plugin
  /// @return pluginConfig The current plugin config as bitmap. Each bit is responsible for enabling/disabling the hooks, the last bit turns on/off dynamic fees logic
  /// @return activeLiquidity  The currently in-range liquidity available to the pool
  /// @return nextTick The next initialized tick after current global tick
  /// @return previousTick The previous initialized tick before (or at) current global tick
  function safelyGetStateOfAMM()
    external
    view
    returns (uint160 sqrtPrice, int24 tick, uint16 lastFee, uint8 pluginConfig, uint128 activeLiquidity, int24 nextTick, int24 previousTick);

  /// @notice Allows to easily get current reentrancy lock status
  /// @dev can be used to prevent read-only reentrancy.
  /// This method just returns `globalState.unlocked` value
  /// @return unlocked Reentrancy lock flag, true if the pool currently is unlocked, otherwise - false
  function isUnlocked() external view returns (bool unlocked);

  // ! IMPORTANT security note: the pool state can be manipulated.
  // ! The following methods do not check reentrancy lock themselves.

  /// @notice The globalState structure in the pool stores many values but requires only one slot
  /// and is exposed as a single method to save gas when accessed externally.
  /// @dev **important security note: caller should check `unlocked` flag to prevent read-only reentrancy**
  /// @return price The current price of the pool as a sqrt(dToken1/dToken0) Q64.96 value
  /// @return tick The current tick of the pool, i.e. according to the last tick transition that was run
  /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(price) if the price is on a tick boundary
  /// @return lastFee The current (last known) pool fee value in hundredths of a bip, i.e. 1e-6 (so '100' is '0.01%'). May be obsolete if using dynamic fee plugin
  /// @return pluginConfig The current plugin config as bitmap. Each bit is responsible for enabling/disabling the hooks, the last bit turns on/off dynamic fees logic
  /// @return communityFee The community fee represented as a percent of all collected fee in thousandths, i.e. 1e-3 (so 100 is 10%)
  /// @return unlocked Reentrancy lock flag, true if the pool currently is unlocked, otherwise - false
  function globalState() external view returns (uint160 price, int24 tick, uint16 lastFee, uint8 pluginConfig, uint16 communityFee, bool unlocked);

  /// @notice Look up information about a specific tick in the pool
  /// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
  /// @param tick The tick to look up
  /// @return liquidityTotal The total amount of position liquidity that uses the pool either as tick lower or tick upper
  /// @return liquidityDelta How much liquidity changes when the pool price crosses the tick
  /// @return prevTick The previous tick in tick list
  /// @return nextTick The next tick in tick list
  /// @return outerFeeGrowth0Token The fee growth on the other side of the tick from the current tick in token0
  /// @return outerFeeGrowth1Token The fee growth on the other side of the tick from the current tick in token1
  /// In addition, these values are only relative and must be used only in comparison to previous snapshots for
  /// a specific position.
  function ticks(
    int24 tick
  )
    external
    view
    returns (
      uint256 liquidityTotal,
      int128 liquidityDelta,
      int24 prevTick,
      int24 nextTick,
      uint256 outerFeeGrowth0Token,
      uint256 outerFeeGrowth1Token
    );

  /// @notice The timestamp of the last sending of tokens to community vault
  /// @return The timestamp truncated to 32 bits
  function communityFeeLastTimestamp() external view returns (uint32);

  /// @notice The amounts of token0 and token1 that will be sent to the vault
  /// @dev Will be sent COMMUNITY_FEE_TRANSFER_FREQUENCY after communityFeeLastTimestamp
  /// @return communityFeePending0 The amount of token0 that will be sent to the vault
  /// @return communityFeePending1 The amount of token1 that will be sent to the vault
  function getCommunityFeePending() external view returns (uint128 communityFeePending0, uint128 communityFeePending1);

  /// @notice Returns the address of currently used plugin
  /// @dev The plugin is subject to change
  /// @return pluginAddress The address of currently used plugin
  function plugin() external view returns (address pluginAddress);

  /// @notice Returns 256 packed tick initialized boolean values. See TickTree for more information
  /// @param wordPosition Index of 256-bits word with ticks
  /// @return The 256-bits word with packed ticks info
  function tickTable(int16 wordPosition) external view returns (uint256);

  /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
  /// @dev This value can overflow the uint256
  /// @return The fee growth accumulator for token0
  function totalFeeGrowth0Token() external view returns (uint256);

  /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
  /// @dev This value can overflow the uint256
  /// @return The fee growth accumulator for token1
  function totalFeeGrowth1Token() external view returns (uint256);

  /// @notice The current pool fee value
  /// @dev In case dynamic fee is enabled in the pool, this method will call the plugin to get the current fee.
  /// If the plugin implements complex fee logic, this method may return an incorrect value or revert.
  /// In this case, see the plugin implementation and related documentation.
  /// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
  /// @return currentFee The current pool fee value in hundredths of a bip, i.e. 1e-6
  function fee() external view returns (uint16 currentFee);

  /// @notice The tracked token0 and token1 reserves of pool
  /// @dev If at any time the real balance is larger, the excess will be transferred to liquidity providers as additional fee.
  /// If the balance exceeds uint128, the excess will be sent to the communityVault.
  /// @return reserve0 The last known reserve of token0
  /// @return reserve1 The last known reserve of token1
  function getReserves() external view returns (uint128 reserve0, uint128 reserve1);

  /// @notice Returns the information about a position by the position's key
  /// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
  /// @param key The position's key is a packed concatenation of the owner address, bottomTick and topTick indexes
  /// @return liquidity The amount of liquidity in the position
  /// @return innerFeeGrowth0Token Fee growth of token0 inside the tick range as of the last mint/burn/poke
  /// @return innerFeeGrowth1Token Fee growth of token1 inside the tick range as of the last mint/burn/poke
  /// @return fees0 The computed amount of token0 owed to the position as of the last mint/burn/poke
  /// @return fees1 The computed amount of token1 owed to the position as of the last mint/burn/poke
  function positions(
    bytes32 key
  ) external view returns (uint256 liquidity, uint256 innerFeeGrowth0Token, uint256 innerFeeGrowth1Token, uint128 fees0, uint128 fees1);

  /// @notice The currently in range liquidity available to the pool
  /// @dev This value has no relationship to the total liquidity across all ticks.
  /// Returned value cannot exceed type(uint128).max
  /// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
  /// @return The current in range liquidity
  function liquidity() external view returns (uint128);

  /// @notice The current tick spacing
  /// @dev Ticks can only be initialized by new mints at multiples of this value
  /// e.g.: a tickSpacing of 60 means ticks can be initialized every 60th tick, i.e., ..., -120, -60, 0, 60, 120, ...
  /// However, tickspacing can be changed after the ticks have been initialized.
  /// This value is an int24 to avoid casting even though it is always positive.
  /// @return The current tick spacing
  function tickSpacing() external view returns (int24);

  /// @notice The previous initialized tick before (or at) current global tick
  /// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
  /// @return The previous initialized tick
  function prevTickGlobal() external view returns (int24);

  /// @notice The next initialized tick after current global tick
  /// @dev **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
  /// @return The next initialized tick
  function nextTickGlobal() external view returns (int24);

  /// @notice The root of tick search tree
  /// @dev Each bit corresponds to one node in the second layer of tick tree: '1' if node has at least one active bit.
  /// **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
  /// @return The root of tick search tree as bitmap
  function tickTreeRoot() external view returns (uint32);

  /// @notice The second layer of tick search tree
  /// @dev Each bit in node corresponds to one node in the leafs layer (`tickTable`) of tick tree: '1' if leaf has at least one active bit.
  /// **important security note: caller should check reentrancy lock to prevent read-only reentrancy**
  /// @return The node of tick search tree second layer
  function tickTreeSecondLayer(int16) external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

/**
 * @dev Interface of the Orchestrator.
 */
interface IOrchestrator {
    enum ActionState {
        PENDING,
        COMPLETED
    }

    /**
     * @dev MUST trigger when actions are executed.
     * @param actionHash: keccak256(targetAddress, jobEpoch, calldatas) used to identify this action
     * @param from: the address of the keeper that executed this action
     * @param rewardPerAction: SteerToken reward for this action, to be supplied to operator nodes.
     */
    event ActionExecuted(
        bytes32 indexed actionHash,
        address from,
        uint256 rewardPerAction
    );
    event ActionFailed(bytes32 indexed actionHash);
    event Vote(
        bytes32 indexed actionHash,
        address indexed from,
        bool approved
    );

    // If an action is approved by >= approvalThresholdPercent members, it is approved
    function actionThresholdPercent() external view returns (uint256);

    // Address of GasVault, which is the contract used to recompense keepers for gas they spent executing actions
    function gasVault() external view returns (address);

    // Address of Keeper Registry, which handles keeper verification
    function keeperRegistry() external view returns (address);

    // Operator node action participation reward. Currently unused.
    function rewardPerAction() external view returns (uint256);

    /*
        bytes32 is hash of action. Calculated using keccak256(abi.encode(targetAddress, jobEpoch, calldatas))

        Action approval meaning:
        0: Pending
        1: Approved
        Both votes and overall approval status follow this standard.
    */
    function actions(bytes32) external view returns (ActionState);

    /*  
        actionHash => uint256 where each bit represents one keeper vote.
    */
    function voteBitmaps(bytes32) external view returns (uint256);

    /**
     * @dev initialize the Orchestrator
     * @param _keeperRegistry address of the keeper registry
     * @param _rewardPerAction is # of SteerToken to give to operator nodes for each completed action (currently unused)
     */
    function initialize(address _keeperRegistry, uint256 _rewardPerAction)
        external;

    /**
     * @dev allows owner to set/update gas vault address. Mainly used to resolve mutual dependency.
     */
    function setGasVault(address _gasVault) external;

    /**
     * @dev set the reward given to operator nodes for their participation in a strategy calculation
     * @param _rewardPerAction is amount of steer token to be earned as a reward, per participating operator node per action.
     */
    function setRewardPerAction(uint256 _rewardPerAction) external;

    /**
     * @dev vote (if you are a keeper) on a given action proposal
     * @param actionHash is the hash of the action to be voted on
     * @param vote is the vote to be cast. false: reject, true: approve. false only has an effect if the keeper previously voted true. It resets their vote to false.
     */
    function voteOnAction(bytes32 actionHash, bool vote) external;

    /**
     * @dev Returns true if an action with given `actionId` is approved by all existing members of the group.
     * It’s up to the contract creators to decide if this method should look at majority votes (based on ownership)
     * or if it should ask consent of all the users irrespective of their ownerships.
     */
    function actionApprovalStatus(bytes32 actionHash)
        external
        view
        returns (bool);

    /**
     * @dev Executes the action referenced by the given `actionId` as long as it is approved actionThresholdPercent of group.
     * The executeAction executes all methods as part of given action in an atomic way (either all should succeed or none should succeed).
     * Once executed, the action should be set as executed (state=3) so that it cannot be executed again.

     * @param targetAddress is the address which will be receiving the action's calls.
     * @param jobEpoch is the job epoch of this action.
     * @param calldatas is the COMPLETE calldata of each method to be called
     * note that the hash is created using the sliced calldata, but here it must be complete or the method will revert.
     * @param timeIndependentLengths--For each calldata, the number of bytes that is NOT time-sensitive. If no calldatas are time-sensitive, just pass an empty array.
     * @param jobHash is the identifier for the job this action is related to. This is used for DynamicJobs to identify separate jobs to the subgraph.
     * @return actionState corresponding to post-execution action state. Pending if execution failed, Completed if execution succeeded.
     */
    function executeAction(
        address targetAddress,
        uint256 jobEpoch,
        bytes[] calldata calldatas,
        uint256[] calldata timeIndependentLengths,
        bytes32 jobHash
    ) external returns (ActionState);
}

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

import { IGasVault } from "./interfaces/IGasVault.sol";
import { IOrchestrator } from "./interfaces/IOrchestrator.sol";
import { IKeeperRegistry } from "./interfaces/IKeeperRegistry.sol";
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

/*
    note there is no current on-chain method for slashing misbehaving strategies. The worst misbehaving strategies can do is trigger repeated calls to this contract.

    note This contract relies on the assumption that jobs can only be created by vault and strategy creators. The most serious incorrect target addresses (the orchestrator
    address and the gasVault address) are blocked, but other vaults are protected by the keepers themselves.
 */
contract Orchestrator is IOrchestrator, OwnableUpgradeable, UUPSUpgradeable {
    uint256 public constant actionThresholdPercent = 51; // If an action is approved by >= approvalThresholdPercent members, it is approved

    //Used for differentiating actions which needs time-sensitive data
    string private constant salt = "$$";

    // Address of GasVault, which is the contract used to recompense keepers for gas they spent executing actions
    address public gasVault;

    // Address of Keeper Registry, which handles keeper verification
    address public keeperRegistry;

    // Operator node action participation reward. Currently unused.
    uint256 public rewardPerAction;

    /*
        bytes32 is hash of action. Calculated using keccak256(abi.encode(targetAddress, jobEpoch, calldatas))

        Action approval meaning:
        0: Pending
        1: Completed
        Both votes and overall approval status follow this standard.
    */
    mapping(bytes32 => ActionState) public actions;

    /*  
        actionHash => uint256 where each bit represents one keeper vote.
    */
    mapping(bytes32 => uint256) public voteBitmaps;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    /**
     * @dev initialize the Orchestrator
     * @param _keeperRegistry address of the keeper registry
     * @param _rewardPerAction is # of SteerToken to give to operator nodes for each completed action (currently unused)
     */
    function initialize(
        address _keeperRegistry,
        uint256 _rewardPerAction
    ) external initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
        require(_keeperRegistry != address(0), "address(0)");
        keeperRegistry = _keeperRegistry;
        rewardPerAction = _rewardPerAction;
    }

    /**
     * @dev allows owner to set/update gas vault address. Mainly used to resolve mutual dependency.
     */
    function setGasVault(address _gasVault) external onlyOwner {
        require(_gasVault != address(0), "address(0)");
        gasVault = _gasVault;
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}

    /**
     * @dev set the reward given to operator nodes for their participation in a strategy calculation
     * @param _rewardPerAction is amount of steer token to be earned as a reward, per participating operator node per action.
     */
    function setRewardPerAction(uint256 _rewardPerAction) external onlyOwner {
        rewardPerAction = _rewardPerAction;
    }

    /**
     * @dev vote (if you are a keeper) on a given action proposal
     * @param actionHash is the hash of the action to be voted on
     * @param vote is the vote to be cast. false: reject, true: approve. false only has an effect if the keeper previously voted true. It resets their vote to false.
     */
    function voteOnAction(bytes32 actionHash, bool vote) public {
        // Get voter keeper license, use to construct bitmap. Revert if no license.
        uint256 license = IKeeperRegistry(keeperRegistry).checkLicense(
            msg.sender
        );
        uint256 bitmap = 1 << license;
        if (vote) {
            // Add vote to bitmap through OR
            voteBitmaps[actionHash] |= bitmap;
        } else {
            // Remove vote from bitmap through And(&) and negated bitmap(~bitmap).
            voteBitmaps[actionHash] &= ~bitmap;
        }
        emit Vote(actionHash, msg.sender, vote);
    }

    /**
     * @dev Returns true if an action with given `actionId` is approved by all existing members of the group.
     * It’s up to the contract creators to decide if this method should look at majority votes (based on ownership)
     * or if it should ask consent of all the users irrespective of their ownerships.
     */
    function actionApprovalStatus(
        bytes32 actionHash
    ) public view returns (bool) {
        /*
            maxLicenseId represents the number at which the below for loop should stop checking the bitmap for votes. It's 1 greater than the last keeper's
            bitmap number so that the loop ends after handling the last keeper.
        */
        uint256 maxLicenseId = IKeeperRegistry(keeperRegistry)
            .maxNumKeepers() + 1;
        uint256 yesVotes;
        uint256 voteDifference;
        uint256 voteBitmap = voteBitmaps[actionHash];
        for (uint256 i = 1; i != maxLicenseId; ++i) {
            // Get bit which this keeper has control over
            voteDifference = 1 << i;

            // If the bit at this keeper's position has been flipped to 1, they approved this action
            if ((voteBitmap & voteDifference) == voteDifference) {
                ++yesVotes;
            }
        }

        // Check current keeper count to get threshold
        uint256 numKeepers = IKeeperRegistry(keeperRegistry)
            .currentNumKeepers();

        // If there happen to be no keepers, div by zero error will happen here, preventing actions from being executed.
        return ((yesVotes * 100) / numKeepers >= actionThresholdPercent);
    }

    /**
     * @dev Executes the action referenced by the given `actionId` as long as it is approved actionThresholdPercent of group.
     * The executeAction executes all methods as part of given action in an atomic way (either all should succeed or none should succeed).
     * Once executed, the action should be set as executed (state=3) so that it cannot be executed again.

     * @param targetAddress is the address which will be receiving the action's calls.
     * @param jobEpoch is the job epoch of this action.
     * @param calldatas is the COMPLETE calldata of each method to be called
     * note that the hash is created using the sliced calldata, but here it must be complete or the method will revert.
     * @param timeIndependentLengths--For each calldata, the number of bytes that is NOT time-sensitive. If no calldatas are time-sensitive, just pass an empty array.
     * @param jobHash is the identifier for the job this action is related to. This is used for DynamicJobs to identify separate jobs to the subgraph.
     * @return actionState corresponding to post-execution action state. Pending if execution failed, Completed if execution succeeded.
     */
    function executeAction(
        address targetAddress,
        uint256 jobEpoch,
        bytes[] calldata calldatas,
        uint256[] calldata timeIndependentLengths,
        bytes32 jobHash
    ) external returns (ActionState) {
        // Make sure this action is approved and has not yet been executed
        bytes32 actionHash;
        if (timeIndependentLengths.length == 0) {
            // If none of the data is time-sensitive, just use passed in calldatas
            actionHash = keccak256(
                abi.encode(targetAddress, jobEpoch, calldatas)
            );
        } else {
            // If some of it is time-sensitive, create a new array using timeIndependentLengths to represent what was originally passed in, then compare that hash instead
            uint256 calldataCount = timeIndependentLengths.length;

            // Construct original calldatas
            bytes[] memory timeIndependentCalldatas = new bytes[](
                calldataCount
            );
            for (uint256 i; i != calldataCount; ++i) {
                timeIndependentCalldatas[i] = calldatas[
                    i
                ][:timeIndependentLengths[i]];
            }

            // Create hash from sliced calldatas
            actionHash = keccak256(
                abi.encode(
                    targetAddress,
                    jobEpoch,
                    timeIndependentCalldatas,
                    salt
                )
            );
        }

        // Ensure action has not yet been executed
        require(
            actions[actionHash] == ActionState.PENDING,
            "Action already executed"
        );

        // Make sure this action isn't illegal (must be checked here, since elsewhere the contract only knows the action hash)
        require(targetAddress != address(this), "Invalid target address");
        require(targetAddress != gasVault, "Invalid target address");

        // Set state to completed
        actions[actionHash] = ActionState.COMPLETED;

        // Have this keeper vote for action. This also checks that the caller is a keeper.
        voteOnAction(actionHash, true);

        // Check action approval status, execute accordingly.
        bool actionApproved = actionApprovalStatus(actionHash);
        if (actionApproved) {
            // Set aside gas for this action. Keeper will be reimbursed ((originalGas - [gas remaining when returnGas is called]) * gasPrice) wei.
            uint256 originalGas = gasleft();

            // Execute action
            (bool success, ) = address(this).call{ // Check gas available for this transaction. This call will fail if gas available is insufficient or this call's gas price is too high.
                gas: IGasVault(gasVault).gasAvailableForTransaction(
                    targetAddress
                )
            }(
                abi.encodeWithSignature(
                    "_executeAction(address,bytes[])",
                    targetAddress,
                    calldatas
                )
            );

            // Reimburse keeper for gas used, whether action execution succeeded or not. The reimbursement will be stored inside the GasVault.
            IGasVault(gasVault).reimburseGas(
                targetAddress,
                originalGas,
                jobHash == bytes32(0) ? actionHash : jobHash // If a jobhash was passed in, use that. Otherwise use the action hash.
            );

            // Record result
            if (success) {
                emit ActionExecuted(actionHash, _msgSender(), rewardPerAction);
                return ActionState.COMPLETED;
            } else {
                emit ActionFailed(actionHash);
                // Set state to pending
                actions[actionHash] = ActionState.PENDING;
                return ActionState.PENDING;
            }
        } else {
            // If action is not approved, revert.
            revert("Votes lacking; state still pending");
        }
    }

    function _executeAction(
        address targetAddress,
        bytes[] calldata calldatas
    ) external {
        require(
            msg.sender == address(this),
            "Only Orchestrator can call this function"
        );

        bool success;
        uint256 calldataCount = calldatas.length;
        for (uint256 i; i != calldataCount; ++i) {
            (success, ) = targetAddress.call(calldatas[i]);

            // If any method fails, the action will revert, reverting all other methods but still pulling gas used from the GasVault.
            require(success);
        }
    }
}

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

pragma solidity ^0.8.2;

import "../beacon/IBeacon.sol";
import "../../interfaces/IERC1967.sol";
import "../../interfaces/draft-IERC1822.sol";
import "../../utils/Address.sol";
import "../../utils/StorageSlot.sol";

/**
 * @dev This abstract contract provides getters and event emitting update functions for
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
 *
 * _Available since v4.1._
 */
abstract contract ERC1967Upgrade is IERC1967 {
    // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
    bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;

    /**
     * @dev Storage slot with the address of the current implementation.
     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    /**
     * @dev Returns the current implementation address.
     */
    function _getImplementation() internal view returns (address) {
        return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 implementation slot.
     */
    function _setImplementation(address newImplementation) private {
        require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
        StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    }

    /**
     * @dev Perform implementation upgrade
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeTo(address newImplementation) internal {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }

    /**
     * @dev Perform implementation upgrade with additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
        _upgradeTo(newImplementation);
        if (data.length > 0 || forceCall) {
            Address.functionDelegateCall(newImplementation, data);
        }
    }

    /**
     * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
        // Upgrades from old implementations will perform a rollback test. This test requires the new
        // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
        // this special case will break upgrade paths from old UUPS implementation to new ones.
        if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
            _setImplementation(newImplementation);
        } else {
            try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
            } catch {
                revert("ERC1967Upgrade: new implementation is not UUPS");
            }
            _upgradeToAndCall(newImplementation, data, forceCall);
        }
    }

    /**
     * @dev Storage slot with the admin of the contract.
     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    /**
     * @dev Returns the current admin.
     */
    function _getAdmin() internal view returns (address) {
        return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 admin slot.
     */
    function _setAdmin(address newAdmin) private {
        require(newAdmin != address(0), "ERC1967: new admin is the zero address");
        StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {AdminChanged} event.
     */
    function _changeAdmin(address newAdmin) internal {
        emit AdminChanged(_getAdmin(), newAdmin);
        _setAdmin(newAdmin);
    }

    /**
     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
     * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
     */
    bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    /**
     * @dev Returns the current beacon.
     */
    function _getBeacon() internal view returns (address) {
        return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
    }

    /**
     * @dev Stores a new beacon in the EIP1967 beacon slot.
     */
    function _setBeacon(address newBeacon) private {
        require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
        require(
            Address.isContract(IBeacon(newBeacon).implementation()),
            "ERC1967: beacon implementation is not a contract"
        );
        StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
    }

    /**
     * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
     * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
     *
     * Emits a {BeaconUpgraded} event.
     */
    function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
        _setBeacon(newBeacon);
        emit BeaconUpgraded(newBeacon);
        if (data.length > 0 || forceCall) {
            Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
        }
    }
}

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

import "./SwapsicleBaseLiquidityManager.sol";

contract SwapsicleMultiPositionLiquidityManager is
    SwapsicleBaseLiquidityManager
{
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract KimBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0x2F0d41f94d5D1550b79A83D2fe85C82d68c5a3ca);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

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

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
  /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
  /// @param a The multiplicand
  /// @param b The multiplier
  /// @param denominator The divisor
  /// @return result The 256-bit result
  /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
  function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
    unchecked {
      // 512-bit multiply [prod1 prod0] = a * b
      // Compute the product mod 2**256 and mod 2**256 - 1
      // then use the Chinese Remainder Theorem to reconstruct
      // the 512 bit result. The result is stored in two 256
      // variables such that product = prod1 * 2**256 + prod0
      uint256 prod0 = a * b; // Least significant 256 bits of the product
      uint256 prod1; // Most significant 256 bits of the product
      assembly {
        let mm := mulmod(a, b, not(0))
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
      }

      // Make sure the result is less than 2**256.
      // Also prevents denominator == 0
      require(denominator > prod1);

      // Handle non-overflow cases, 256 by 256 division
      if (prod1 == 0) {
        assembly {
          result := div(prod0, denominator)
        }
        return result;
      }

      ///////////////////////////////////////////////
      // 512 by 256 division.
      ///////////////////////////////////////////////

      // Make division exact by subtracting the remainder from [prod1 prod0]
      // Compute remainder using mulmod
      // Subtract 256 bit remainder from 512 bit number
      assembly {
        let remainder := mulmod(a, b, denominator)
        prod1 := sub(prod1, gt(remainder, prod0))
        prod0 := sub(prod0, remainder)
      }

      // Factor powers of two out of denominator
      // Compute largest power of two divisor of denominator.
      // Always >= 1.
      uint256 twos = (0 - denominator) & denominator;
      // Divide denominator by power of two
      assembly {
        denominator := div(denominator, twos)
      }

      // Divide [prod1 prod0] by the factors of two
      assembly {
        prod0 := div(prod0, twos)
      }
      // Shift in bits from prod1 into prod0. For this we need
      // to flip `twos` such that it is 2**256 / twos.
      // If twos is zero, then it becomes one
      assembly {
        twos := add(div(sub(0, twos), twos), 1)
      }
      prod0 |= prod1 * twos;

      // Invert denominator mod 2**256
      // Now that denominator is an odd number, it has an inverse
      // modulo 2**256 such that denominator * inv = 1 mod 2**256.
      // Compute the inverse by starting with a seed that is correct
      // correct for four bits. That is, denominator * inv = 1 mod 2**4
      uint256 inv = (3 * denominator) ^ 2;
      // Now use Newton-Raphson iteration to improve the precision.
      // Thanks to Hensel's lifting lemma, this also works in modular
      // arithmetic, doubling the correct bits in each step.
      inv *= 2 - denominator * inv; // inverse mod 2**8
      inv *= 2 - denominator * inv; // inverse mod 2**16
      inv *= 2 - denominator * inv; // inverse mod 2**32
      inv *= 2 - denominator * inv; // inverse mod 2**64
      inv *= 2 - denominator * inv; // inverse mod 2**128
      inv *= 2 - denominator * inv; // inverse mod 2**256

      // Because the division is now exact we can divide by multiplying
      // with the modular inverse of denominator. This will give us the
      // correct result modulo 2**256. Since the preconditions guarantee
      // that the outcome is less than 2**256, this is the final result.
      // We don't need to compute the high bits of the result and prod1
      // is no longer required.
      result = prod0 * inv;
      return result;
    }
  }

  /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
  /// @param a The multiplicand
  /// @param b The multiplier
  /// @param denominator The divisor
  /// @return result The 256-bit result
  function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
    unchecked {
      if (a == 0 || ((result = a * b) / a == b)) {
        require(denominator > 0);
        assembly {
          result := add(div(result, denominator), gt(mod(result, denominator), 0))
        }
      } else {
        result = mulDiv(a, b, denominator);
        if (mulmod(a, b, denominator) > 0) {
          require(result < type(uint256).max);
          result++;
        }
      }
    }
  }

  /// @notice Returns ceil(x / y)
  /// @dev division by 0 has unspecified behavior, and must be checked externally
  /// @param x The dividend
  /// @param y The divisor
  /// @return z The quotient, ceil(x / y)
  function unsafeDivRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
    assembly {
      z := add(div(x, y), gt(mod(x, y), 0))
    }
  }
}

File 103 of 177 : IAlgebraPoolEvents.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Events emitted by a pool
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolEvents {
  /// @notice Emitted exactly once by a pool when #initialize is first called on the pool
  /// @dev Mint/Burn/Swaps cannot be emitted by the pool before Initialize
  /// @param price The initial sqrt price of the pool, as a Q64.96
  /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool
  event Initialize(uint160 price, int24 tick);

  /// @notice Emitted when liquidity is minted for a given position
  /// @param sender The address that minted the liquidity
  /// @param owner The owner of the position and recipient of any minted liquidity
  /// @param bottomTick The lower tick of the position
  /// @param topTick The upper tick of the position
  /// @param liquidityAmount The amount of liquidity minted to the position range
  /// @param amount0 How much token0 was required for the minted liquidity
  /// @param amount1 How much token1 was required for the minted liquidity
  event Mint(
    address sender,
    address indexed owner,
    int24 indexed bottomTick,
    int24 indexed topTick,
    uint128 liquidityAmount,
    uint256 amount0,
    uint256 amount1
  );

  /// @notice Emitted when fees are collected by the owner of a position
  /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees
  /// @param owner The owner of the position for which fees are collected
  /// @param recipient The address that received fees
  /// @param bottomTick The lower tick of the position
  /// @param topTick The upper tick of the position
  /// @param amount0 The amount of token0 fees collected
  /// @param amount1 The amount of token1 fees collected
  event Collect(address indexed owner, address recipient, int24 indexed bottomTick, int24 indexed topTick, uint128 amount0, uint128 amount1);

  /// @notice Emitted when a position's liquidity is removed
  /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect
  /// @param owner The owner of the position for which liquidity is removed
  /// @param bottomTick The lower tick of the position
  /// @param topTick The upper tick of the position
  /// @param liquidityAmount The amount of liquidity to remove
  /// @param amount0 The amount of token0 withdrawn
  /// @param amount1 The amount of token1 withdrawn
  event Burn(address indexed owner, int24 indexed bottomTick, int24 indexed topTick, uint128 liquidityAmount, uint256 amount0, uint256 amount1);

  /// @notice Emitted by the pool for any swaps between token0 and token1
  /// @param sender The address that initiated the swap call, and that received the callback
  /// @param recipient The address that received the output of the swap
  /// @param amount0 The delta of the token0 balance of the pool
  /// @param amount1 The delta of the token1 balance of the pool
  /// @param price The sqrt(price) of the pool after the swap, as a Q64.96
  /// @param liquidity The liquidity of the pool after the swap
  /// @param tick The log base 1.0001 of price of the pool after the swap
  event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick);

  /// @notice Emitted by the pool for any flashes of token0/token1
  /// @param sender The address that initiated the swap call, and that received the callback
  /// @param recipient The address that received the tokens from flash
  /// @param amount0 The amount of token0 that was flashed
  /// @param amount1 The amount of token1 that was flashed
  /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
  /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee
  event Flash(address indexed sender, address indexed recipient, uint256 amount0, uint256 amount1, uint256 paid0, uint256 paid1);

  /// @notice Emitted when the community fee is changed by the pool
  /// @param communityFeeNew The updated value of the community fee in thousandths (1e-3)
  event CommunityFee(uint16 communityFeeNew);

  /// @notice Emitted when the tick spacing changes
  /// @param newTickSpacing The updated value of the new tick spacing
  event TickSpacing(int24 newTickSpacing);

  /// @notice Emitted when the plugin address changes
  /// @param newPluginAddress New plugin address
  event Plugin(address newPluginAddress);

  /// @notice Emitted when the plugin config changes
  /// @param newPluginConfig New plugin config
  event PluginConfig(uint8 newPluginConfig);

  /// @notice Emitted when the fee changes inside the pool
  /// @param fee The current fee in hundredths of a bip, i.e. 1e-6
  event Fee(uint16 fee);
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/**
 * @title Permissioned pool actions
 * @notice Contains pool methods that may only be called by the factory owner or tokenomics
 * @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
 * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
 */
interface IAlgebraPoolPermissionedActions {
  /**
   * @notice Set the community's % share of the fees. Cannot exceed 25% (250)
   * @param communityFee0 new community fee percent for token0 of the pool in thousandths (1e-3)
   * @param communityFee1 new community fee percent for token1 of the pool in thousandths (1e-3)
   */
  function setCommunityFee(uint8 communityFee0, uint8 communityFee1) external;

  /// @notice Set the new tick spacing values. Only factory owner
  /// @param newTickSpacing The new tick spacing value
  function setTickSpacing(int24 newTickSpacing) external;

  /**
   * @notice Sets an active incentive
   * @param virtualPoolAddress The address of a virtual pool associated with the incentive
   */
  function setIncentive(address virtualPoolAddress) external;

  /**
   * @notice Sets new lock time for added liquidity
   * @param newLiquidityCooldown The time in seconds
   */
  function setLiquidityCooldown(uint32 newLiquidityCooldown) external;
}

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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "./interfaces/IBeaconInterface.sol";

contract Beacon is IBeaconInterface, Ownable {
    // Storage

    /// @dev Current implementation address for this beacon,
    ///      i.e. the address which all beacon proxies will delegatecall to.
    address public implementation;

    // Constructor

    /// @param implementationAddress The address all beaconProxies will delegatecall to.
    constructor(address implementationAddress) {
        _setImplementationAddress(implementationAddress);
    }

    /// @dev Upgrades the implementation address of the beacon or the address that the beacon points to.
    /// @param newImplementation Address of implementation that the beacon should be upgraded to.
    function upgradeImplementationTo(address newImplementation)
        public
        virtual
        onlyOwner
    {
        _setImplementationAddress(newImplementation);
    }

    function _setImplementationAddress(address newImplementation) internal {
        require(
            Address.isContract(newImplementation),
            "UpgradeableBeacon: implementation is not a contract"
        );
        implementation = newImplementation;
        emit Upgraded(newImplementation);
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissionless pool actions
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolActions {
  /// @notice Sets the initial price for the pool
  /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
  /// @dev Initialization should be done in one transaction with pool creation to avoid front-running
  /// @param initialPrice The initial sqrt price of the pool as a Q64.96
  function initialize(uint160 initialPrice) external;

  /// @notice Adds liquidity for the given recipient/bottomTick/topTick position
  /// @dev The caller of this method receives a callback in the form of IAlgebraMintCallback# AlgebraMintCallback
  /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
  /// on bottomTick, topTick, the amount of liquidity, and the current price.
  /// @param leftoversRecipient The address which will receive potential surplus of paid tokens
  /// @param recipient The address for which the liquidity will be created
  /// @param bottomTick The lower tick of the position in which to add liquidity
  /// @param topTick The upper tick of the position in which to add liquidity
  /// @param liquidityDesired The desired amount of liquidity to mint
  /// @param data Any data that should be passed through to the callback
  /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
  /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
  /// @return liquidityActual The actual minted amount of liquidity
  function mint(
    address leftoversRecipient,
    address recipient,
    int24 bottomTick,
    int24 topTick,
    uint128 liquidityDesired,
    bytes calldata data
  ) external returns (uint256 amount0, uint256 amount1, uint128 liquidityActual);

  /// @notice Collects tokens owed to a position
  /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
  /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
  /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
  /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
  /// @param recipient The address which should receive the fees collected
  /// @param bottomTick The lower tick of the position for which to collect fees
  /// @param topTick The upper tick of the position for which to collect fees
  /// @param amount0Requested How much token0 should be withdrawn from the fees owed
  /// @param amount1Requested How much token1 should be withdrawn from the fees owed
  /// @return amount0 The amount of fees collected in token0
  /// @return amount1 The amount of fees collected in token1
  function collect(
    address recipient,
    int24 bottomTick,
    int24 topTick,
    uint128 amount0Requested,
    uint128 amount1Requested
  ) external returns (uint128 amount0, uint128 amount1);

  /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
  /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
  /// @dev Fees must be collected separately via a call to #collect
  /// @param bottomTick The lower tick of the position for which to burn liquidity
  /// @param topTick The upper tick of the position for which to burn liquidity
  /// @param amount How much liquidity to burn
  /// @param data Any data that should be passed through to the plugin
  /// @return amount0 The amount of token0 sent to the recipient
  /// @return amount1 The amount of token1 sent to the recipient
  function burn(int24 bottomTick, int24 topTick, uint128 amount, bytes calldata data) external returns (uint256 amount0, uint256 amount1);

  /// @notice Swap token0 for token1, or token1 for token0
  /// @dev The caller of this method receives a callback in the form of IAlgebraSwapCallback#AlgebraSwapCallback
  /// @param recipient The address to receive the output of the swap
  /// @param zeroToOne The direction of the swap, true for token0 to token1, false for token1 to token0
  /// @param amountRequired The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
  /// @param limitSqrtPrice The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
  /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
  /// @param data Any data to be passed through to the callback. If using the Router it should contain SwapRouter#SwapCallbackData
  /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
  /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
  function swap(
    address recipient,
    bool zeroToOne,
    int256 amountRequired,
    uint160 limitSqrtPrice,
    bytes calldata data
  ) external returns (int256 amount0, int256 amount1);

  /// @notice Swap token0 for token1, or token1 for token0 with prepayment
  /// @dev The caller of this method receives a callback in the form of IAlgebraSwapCallback#AlgebraSwapCallback
  /// caller must send tokens in callback before swap calculation
  /// the actually sent amount of tokens is used for further calculations
  /// @param leftoversRecipient The address which will receive potential surplus of paid tokens
  /// @param recipient The address to receive the output of the swap
  /// @param zeroToOne The direction of the swap, true for token0 to token1, false for token1 to token0
  /// @param amountToSell The amount of the swap, only positive (exact input) amount allowed
  /// @param limitSqrtPrice The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
  /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
  /// @param data Any data to be passed through to the callback. If using the Router it should contain SwapRouter#SwapCallbackData
  /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
  /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
  function swapWithPaymentInAdvance(
    address leftoversRecipient,
    address recipient,
    bool zeroToOne,
    int256 amountToSell,
    uint160 limitSqrtPrice,
    bytes calldata data
  ) external returns (int256 amount0, int256 amount1);

  /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
  /// @dev The caller of this method receives a callback in the form of IAlgebraFlashCallback#AlgebraFlashCallback
  /// @dev All excess tokens paid in the callback are distributed to currently in-range liquidity providers as an additional fee.
  /// If there are no in-range liquidity providers, the fee will be transferred to the first active provider in the future
  /// @param recipient The address which will receive the token0 and token1 amounts
  /// @param amount0 The amount of token0 to send
  /// @param amount1 The amount of token1 to send
  /// @param data Any data to be passed through to the callback
  function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
}

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

pragma solidity ^0.8.0;

import "./IERC721Upgradeable.sol";
import "./IERC721ReceiverUpgradeable.sol";
import "./extensions/IERC721MetadataUpgradeable.sol";
import "../../utils/AddressUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import "../../utils/StringsUpgradeable.sol";
import "../../utils/introspection/ERC165Upgradeable.sol";
import "../../proxy/utils/Initializable.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 ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable {
    using AddressUpgradeable for address;
    using StringsUpgradeable 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.
     */
    function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __ERC721_init_unchained(name_, symbol_);
    }

    function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {
        return
            interfaceId == type(IERC721Upgradeable).interfaceId ||
            interfaceId == type(IERC721MetadataUpgradeable).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 = ERC721Upgradeable.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 {
        _setApprovalForAll(_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 = ERC721Upgradeable.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 = ERC721Upgradeable.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(ERC721Upgradeable.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(ERC721Upgradeable.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits a {ApprovalForAll} event.
     */
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @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 IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721ReceiverUpgradeable.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 {}
    uint256[44] private __gap;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface IGasVault {
    event Deposited(
        address indexed origin,
        address indexed target,
        uint256 amount
    );
    event Withdrawn(
        address indexed targetAddress,
        address indexed to,
        uint256 amount
    );
    event EtherUsed(address indexed account, uint256 amount, bytes32 jobHash);

    function deposit(address targetAddress) external payable;

    function withdraw(uint256 amount, address payable to) external;

    /**
     * @dev calculates total transactions remaining. What this means is--assuming that each method (action paid for by the strategist/job owner)
     *      costs max amount of gas at max gas price, and uses the max amount of actions, how many transactions can be paid for?
     *      In other words, how many actions can this vault guarantee.
     * @param targetAddress is address actions will be performed on, and address paying gas for those actions.
     * @param highGasEstimate is highest reasonable gas price assumed for the actions
     * @return total transactions remaining, assuming max gas is used in each Method
     */
    function transactionsRemaining(
        address targetAddress,
        uint256 highGasEstimate
    ) external view returns (uint256);

    /**
     * @param targetAddress is address actions will be performed on, and address paying gas for those actions.
     * @return uint256 gasAvailable (representing amount of gas available per Method).
     */
    function gasAvailableForTransaction(
        address targetAddress
    ) external view returns (uint256);

    /**
     * @param targetAddress is address actions were performed on
     * @param originalGas is gas passed in to the action execution order. Used to calculate gas used in the execution.
     * @dev should only ever be called by the orchestrator. Is onlyOrchestrator. This and setAsideGas are used to pull gas from the vault for strategy executions.
     */
    function reimburseGas(
        address targetAddress,
        uint256 originalGas,
        bytes32 newActionHash
    ) external;
}

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

pragma solidity ^0.8.0;

import "./draft-ERC20PermitUpgradeable.sol";
import "../../../utils/math/MathUpgradeable.sol";
import "../../../utils/math/SafeCastUpgradeable.sol";
import "../../../utils/cryptography/ECDSAUpgradeable.sol";
import "../../../proxy/utils/Initializable.sol";

/**
 * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's,
 * and supports token supply up to 2^224^ - 1, while COMP is limited to 2^96^ - 1.
 *
 * NOTE: If exact COMP compatibility is required, use the {ERC20VotesComp} variant of this module.
 *
 * This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either
 * by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting
 * power can be queried through the public accessors {getVotes} and {getPastVotes}.
 *
 * By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it
 * requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked.
 * Enabling self-delegation can easily be done by overriding the {delegates} function. Keep in mind however that this
 * will significantly increase the base gas cost of transfers.
 *
 * _Available since v4.2._
 */
abstract contract ERC20VotesUpgradeable is Initializable, ERC20PermitUpgradeable {
    function __ERC20Votes_init_unchained() internal onlyInitializing {
    }
    struct Checkpoint {
        uint32 fromBlock;
        uint224 votes;
    }

    bytes32 private constant _DELEGATION_TYPEHASH =
        keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");

    mapping(address => address) private _delegates;
    mapping(address => Checkpoint[]) private _checkpoints;
    Checkpoint[] private _totalSupplyCheckpoints;

    /**
     * @dev Emitted when an account changes their delegate.
     */
    event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

    /**
     * @dev Emitted when a token transfer or delegate change results in changes to an account's voting power.
     */
    event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);

    /**
     * @dev Get the `pos`-th checkpoint for `account`.
     */
    function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoint memory) {
        return _checkpoints[account][pos];
    }

    /**
     * @dev Get number of checkpoints for `account`.
     */
    function numCheckpoints(address account) public view virtual returns (uint32) {
        return SafeCastUpgradeable.toUint32(_checkpoints[account].length);
    }

    /**
     * @dev Get the address `account` is currently delegating to.
     */
    function delegates(address account) public view virtual returns (address) {
        return _delegates[account];
    }

    /**
     * @dev Gets the current votes balance for `account`
     */
    function getVotes(address account) public view returns (uint256) {
        uint256 pos = _checkpoints[account].length;
        return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes;
    }

    /**
     * @dev Retrieve the number of votes for `account` at the end of `blockNumber`.
     *
     * Requirements:
     *
     * - `blockNumber` must have been already mined
     */
    function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
        require(blockNumber < block.number, "ERC20Votes: block not yet mined");
        return _checkpointsLookup(_checkpoints[account], blockNumber);
    }

    /**
     * @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances.
     * It is but NOT the sum of all the delegated votes!
     *
     * Requirements:
     *
     * - `blockNumber` must have been already mined
     */
    function getPastTotalSupply(uint256 blockNumber) public view returns (uint256) {
        require(blockNumber < block.number, "ERC20Votes: block not yet mined");
        return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber);
    }

    /**
     * @dev Lookup a value in a list of (sorted) checkpoints.
     */
    function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256) {
        // We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
        //
        // During the loop, the index of the wanted checkpoint remains in the range [low-1, high).
        // With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the invariant.
        // - If the middle checkpoint is after `blockNumber`, we look in [low, mid)
        // - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high)
        // Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not
        // out of bounds (in which case we're looking too far in the past and the result is 0).
        // Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is
        // past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out
        // the same.
        uint256 high = ckpts.length;
        uint256 low = 0;
        while (low < high) {
            uint256 mid = MathUpgradeable.average(low, high);
            if (ckpts[mid].fromBlock > blockNumber) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        return high == 0 ? 0 : ckpts[high - 1].votes;
    }

    /**
     * @dev Delegate votes from the sender to `delegatee`.
     */
    function delegate(address delegatee) public virtual {
        _delegate(_msgSender(), delegatee);
    }

    /**
     * @dev Delegates votes from signer to `delegatee`
     */
    function delegateBySig(
        address delegatee,
        uint256 nonce,
        uint256 expiry,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(block.timestamp <= expiry, "ERC20Votes: signature expired");
        address signer = ECDSAUpgradeable.recover(
            _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))),
            v,
            r,
            s
        );
        require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce");
        _delegate(signer, delegatee);
    }

    /**
     * @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1).
     */
    function _maxSupply() internal view virtual returns (uint224) {
        return type(uint224).max;
    }

    /**
     * @dev Snapshots the totalSupply after it has been increased.
     */
    function _mint(address account, uint256 amount) internal virtual override {
        super._mint(account, amount);
        require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes");

        _writeCheckpoint(_totalSupplyCheckpoints, _add, amount);
    }

    /**
     * @dev Snapshots the totalSupply after it has been decreased.
     */
    function _burn(address account, uint256 amount) internal virtual override {
        super._burn(account, amount);

        _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount);
    }

    /**
     * @dev Move voting power when tokens are transferred.
     *
     * Emits a {DelegateVotesChanged} event.
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        super._afterTokenTransfer(from, to, amount);

        _moveVotingPower(delegates(from), delegates(to), amount);
    }

    /**
     * @dev Change delegation for `delegator` to `delegatee`.
     *
     * Emits events {DelegateChanged} and {DelegateVotesChanged}.
     */
    function _delegate(address delegator, address delegatee) internal virtual {
        address currentDelegate = delegates(delegator);
        uint256 delegatorBalance = balanceOf(delegator);
        _delegates[delegator] = delegatee;

        emit DelegateChanged(delegator, currentDelegate, delegatee);

        _moveVotingPower(currentDelegate, delegatee, delegatorBalance);
    }

    function _moveVotingPower(
        address src,
        address dst,
        uint256 amount
    ) private {
        if (src != dst && amount > 0) {
            if (src != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount);
                emit DelegateVotesChanged(src, oldWeight, newWeight);
            }

            if (dst != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[dst], _add, amount);
                emit DelegateVotesChanged(dst, oldWeight, newWeight);
            }
        }
    }

    function _writeCheckpoint(
        Checkpoint[] storage ckpts,
        function(uint256, uint256) view returns (uint256) op,
        uint256 delta
    ) private returns (uint256 oldWeight, uint256 newWeight) {
        uint256 pos = ckpts.length;
        oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes;
        newWeight = op(oldWeight, delta);

        if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) {
            ckpts[pos - 1].votes = SafeCastUpgradeable.toUint224(newWeight);
        } else {
            ckpts.push(Checkpoint({fromBlock: SafeCastUpgradeable.toUint32(block.number), votes: SafeCastUpgradeable.toUint224(newWeight)}));
        }
    }

    function _add(uint256 a, uint256 b) private pure returns (uint256) {
        return a + b;
    }

    function _subtract(uint256 a, uint256 b) private pure returns (uint256) {
        return a - b;
    }
    uint256[47] private __gap;
}

File 110 of 177 : IERC721ReceiverUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721Receiver.sol)

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

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that never changes
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolImmutables {
  /// @notice The Algebra factory contract, which must adhere to the IAlgebraFactory interface
  /// @return The contract address
  function factory() external view returns (address);

  /// @notice The first of the two tokens of the pool, sorted by address
  /// @return The token contract address
  function token0() external view returns (address);

  /// @notice The second of the two tokens of the pool, sorted by address
  /// @return The token contract address
  function token1() external view returns (address);

  /// @notice The contract to which community fees are transferred
  /// @return The communityVault address
  function communityVault() external view returns (address);

  /// @notice The maximum amount of position liquidity that can use any tick in the range
  /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and
  /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool
  /// @return The max amount of liquidity per tick
  function maxLiquidityPerTick() external view returns (uint128);
}

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

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

pragma solidity ^0.8.0;

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

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

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

File 114 of 177 : draft-IERC1822.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)

pragma solidity ^0.8.0;

/**
 * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
 * proxy whose upgrades are fully controlled by the current implementation.
 */
interface IERC1822Proxiable {
    /**
     * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
     * address.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy.
     */
    function proxiableUUID() external view returns (bytes32);
}

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

// ERC support
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import {IAlgebraFactory} from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import {IAlgebraPool} from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import {TickMath} from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import {FullMath} from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import {IAlgebraMintCallback} from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import {IAlgebraSwapCallback} from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import {LiquidityAmounts} from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import {PositionKey} from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import {IVolatilityOracle} from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {StringsUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import {IBareVaultRegistry8} from "contracts/interfaces/IBareVaultRegistry8.sol";
import {IBaseDeposit} from "contracts/interfaces/IBaseDeposit.sol";
import {IFeeManager} from "../../interfaces/IFeeManager.sol";

abstract contract BeamBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY = IAlgebraFactory(0x28b5244B6CA7Cb07f2f7F40edE944c07C2395603);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(uint160 sqrtPriceX96, uint256 totalAmount0, uint256 totalAmount1, uint256 totalSupply);

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(address indexed sender, address indexed to, uint256 shares, uint256 amount0, uint256 amount1);

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(address indexed sender, address indexed to, uint256 shares, uint256 amount0, uint256 amount1);

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(uint256 shares, uint256 amount0Min, uint256 amount1Min, address to)
        external
        virtual
        returns (uint256 amount0, uint256 amount1)
    {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(shares, _totalSupply);

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(uint256 amount0, uint256 amount1, bytes calldata /* data */ ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(int256 amount0Wanted, int256 amount1Wanted, bytes calldata /* data */ )
        external
        override
    {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(int24 tickLower, int24 tickUpper, uint128 liquidity)
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(address(this), tickLower, tickUpper, type(uint128).max, type(uint128).max);
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
        return IBareVaultRegistry8(vaultRegistry).doISupportInterface(interfaceId);
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (address _token0, address _token1, int24 _maxTickChange, uint32 _twapInterval) =
            abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(_maxTickChange > MAX_TICK_CHANGE_MIN && _maxTickChange < MAX_TICK_CHANGE_MAX);
        require(_twapInterval > TWAP_INTERVAL_MIN && _twapInterval < TWAP_INTERVAL_MAX);
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(IBareVaultRegistry8(msg.sender).totalVaultCount() + 1);
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(uint256 amount0Desired, uint256 amount1Desired, uint256 amount0Min, uint256 amount1Min, address to)
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(vaultRegistry).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold || state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(shares, total1, _totalSupply);
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(shares, total0, _totalSupply);
        } else {
            uint256 cross = Math.min(amount0Desired * total1, amount1Desired * total0);

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(int24 tickLower, int24 tickUpper)
        internal
        view
        returns (uint256, uint256, uint256, uint128, uint128)
    {
        return pool.positions(PositionKey.compute(address(this), tickLower, tickUpper));
    }

    function _transferTokens(address to, uint256 amount0, uint256 amount1) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(uint256 shares, uint256 totalShares) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(secondsAgos) returns (
            int56[] memory tickCumulatives, uint88[] memory
        ) {
            int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];

            int24 twapTick = int24(tickCumulativesDelta / int32(_twapInterval));

            // Always round to negative infinity
            if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(currentTick <= twapTick + _maxTickChange && currentTick >= twapTick - _maxTickChange, "V");
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(string memory feeIdentifier, uint256 amount0, uint256 amount1) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(address(this), feeIdentifier);
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails() external view returns (uint256, address[] memory, string[] memory, uint256[] memory) {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry).getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.10;
pragma abicoder v2;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import { IAlgebraPool } from "@cryptoalgebra/core/contracts/interfaces/IAlgebraPool.sol";

interface IMultiPositionManager is IERC20 {
    struct VaultDetails {
        string vaultType;
        address token0;
        address token1;
        string name;
        string symbol;
        uint256 decimals;
        string token0Name;
        string token1Name;
        string token0Symbol;
        string token1Symbol;
        uint256 token0Decimals;
        uint256 token1Decimals;
        uint256 feeTier;
        uint256 totalLPTokensIssued;
        uint256 token0Balance;
        uint256 token1Balance;
        address vaultCreator;
    }

    struct AlgebraVaultDetails {
        string vaultType;
        address token0;
        address token1;
        string name;
        string symbol;
        uint256 decimals;
        string token0Name;
        string token1Name;
        string token0Symbol;
        string token1Symbol;
        uint256 token0Decimals;
        uint256 token1Decimals;
        uint256 totalLPTokensIssued;
        uint256 token0Balance;
        uint256 token1Balance;
        address vaultCreator;
    }

    struct VaultBalance {
        uint256 amountToken0;
        uint256 amountToken1;
    }

    struct LiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }

    /**
     * @dev initializes vault
     * param _vaultManager is the address which will manage the vault being created
     * param _params is all other parameters this vault will use.
     * param _tokenName is the name of the LPT of this vault.
     * param _symbol is the symbol of the LPT of this vault.
     * param token0 is address of token0
     * param token1 is address of token1
     * param _FEE is pool fee, how much is charged for a swap
     */
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) external;

    ///
    /// @dev Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    /// Uniswap until the next rebalance.
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting `amount0` is less than this
    /// @param amount1Min Revert if resulting `amount1` is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0 Amount of token0 deposited
    /// @return amount1 Amount of token1 deposited
    ///
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external returns (uint256 shares, uint256 amount0, uint256 amount1);

    /**
     * @dev burns each vault position which contains liquidity, updating fees owed to that position.
     * @dev call this before calling getTotalAmounts if total amounts must include fees. There's a function in the periphery to do so through a static call.
     */
    function poke() external;

    /**
     * @dev Withdraws tokens in proportion to the vault's holdings.
     * @param shares Shares burned by sender
     * @param amount0Min Revert if resulting `amount0` is smaller than this
     * @param amount1Min Revert if resulting `amount1` is smaller than this
     * @param to Recipient of tokens
     * @return amount0 Amount of token0 sent to recipient
     * @return amount1 Amount of token1 sent to recipient
     */
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external returns (uint256 amount0, uint256 amount1);

    /**
     * @dev Internal function to pull funds from pool, update positions if necessary, then deposit funds into pool.
     * @dev reverts if it does not have any liquidity.

     * @dev newPositions requirements:
     * Each lowerTick must be lower than its corresponding upperTick
     * Each lowerTick must be greater than or equal to the tick min (-887272)
     * Each upperTick must be less than or equal to the tick max (887272)
     * All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
        A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60. and 1% has tick spacing 200.)
     */
    function tend(
        LiquidityPositions memory newPositions,
        int256 swapAmount,
        uint160 sqrtPriceLimitX96
    ) external;

    /**
     * @dev Calculates the vault's total holdings of token0 and token1 - in
     *      other words, how much of each token the vault would hold if it withdrew
     *      all its liquidity from Uniswap.
     * @dev this function DOES NOT include fees. To include fees, first poke() and then call getTotalAmounts. There's a function inside the periphery to do so.
     */
    function getTotalAmounts()
        external
        view
        returns (uint256 total0, uint256 total1);

    //Tokens
    function vaultRegistry() external view returns (address);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function maxTotalSupply() external view returns (uint256);

    function pool() external view returns (IUniswapV3Pool);

    function TOTAL_FEE() external view returns (uint256);

    function STEER_FRACTION_OF_FEE() external view returns (uint256);

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory);

    // /**
    //  * @dev Used to collect accumulated protocol fees.
    //  */
    // function steerCollectFees(
    //     uint256 amount0,
    //     uint256 amount1,
    //     address to
    // ) external;

    // /**
    //  * @dev Used to collect accumulated protocol fees.
    //  */
    // function strategistCollectFees(
    //     uint256 amount0,
    //     uint256 amount1,
    //     address to
    // ) external;

    /**
     * @dev Used to collect accumulated protocol fees.
     */
    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external;

    /**
     * @dev Removes liquidity in case of emergency.
     */
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    ) external returns (uint256 amount0, uint256 amount1);

    function accruedFees0(string memory) external view returns (uint256);

    function accruedFees1(string memory) external view returns (uint256);
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Callback for IAlgebraPoolActions#swap
/// @notice Any contract that calls IAlgebraPoolActions#swap must implement this interface
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraSwapCallback {
  /// @notice Called to `msg.sender` after executing a swap via IAlgebraPool#swap.
  /// @dev In the implementation you must pay the pool tokens owed for the swap.
  /// The caller of this method _must_ be checked to be a AlgebraPool deployed by the canonical AlgebraFactory.
  /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
  /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
  /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
  /// @param data Any data passed through by the caller via the IAlgebraPoolActions#swap call
  function algebraSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface IBundleRegistry {
    // Need to update contract!
    // Data Source Adapter
    // source: Source name, example: TheGraph
    // host: Host url or ip, example: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3
    // output: The output type of the adapter, example: OHLC, OHLCV, SingleValue, etc.
    // bundle: (cid example: QmVj...DAwMA)
    // active: determines if the adapter is active or not, allows for inactive sources to be deprecated, vaults can pause based on this
    struct DataSourceAdapter {
        string bundle;
        string source;
        string host;
        string output;
        string info;
        bool active;
        address author;
    }

    event BundleRegistered(
        bytes32 hash,
        string bundle,
        string host,
        string source,
        string output,
        string infoHash,
        bool active,
        address creator
    );

    event BundleStateChange(bytes32 hash, bool toggle);

    /// @dev Registers an execution bundle, printing an NFT and mapping to execution bundle and host.
    /// @param _bundle the bundle of the transformation module.
    /// @param _source The host of the transformation module source (e.g. "Uniswap")
    /// @param _host The host of the transformation module source (e.g. "Uniswap")
    /// @param _output The output type of the adapter, example: OHLC, OHLCV, SingleValue, etc.
    /// @param _active determines if the adapter is active or not, allows for inactive sources to be deprecated, vaults can pause based on this
    function register(
        string memory _bundle,
        string memory _source,
        string memory _host,
        string memory _output,
        string memory _infoHash,
        bool _active
    ) external;

    /// @dev Pauses the registeration of bundles
    function pause() external;

    function setAdapterState(bytes32 _adapter, bool _remainActive) external;
}

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

pragma solidity ^0.8.0;

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

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
 * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
 * they need in their contracts using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * _Available since v3.4._
 */
abstract contract EIP712Upgradeable is Initializable {
    /* solhint-disable var-name-mixedcase */
    bytes32 private _HASHED_NAME;
    bytes32 private _HASHED_VERSION;
    bytes32 private constant _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    /* solhint-enable var-name-mixedcase */

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
        __EIP712_init_unchained(name, version);
    }

    function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
        bytes32 hashedName = keccak256(bytes(name));
        bytes32 hashedVersion = keccak256(bytes(version));
        _HASHED_NAME = hashedName;
        _HASHED_VERSION = hashedVersion;
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        return _buildDomainSeparator(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash());
    }

    function _buildDomainSeparator(
        bytes32 typeHash,
        bytes32 nameHash,
        bytes32 versionHash
    ) private view returns (bytes32) {
        return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /**
     * @dev The hash of the name parameter for the EIP712 domain.
     *
     * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
     * are a concern.
     */
    function _EIP712NameHash() internal virtual view returns (bytes32) {
        return _HASHED_NAME;
    }

    /**
     * @dev The hash of the version parameter for the EIP712 domain.
     *
     * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
     * are a concern.
     */
    function _EIP712VersionHash() internal virtual view returns (bytes32) {
        return _HASHED_VERSION;
    }
    uint256[50] private __gap;
}

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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

interface IBlast {
    enum YieldMode {
        AUTOMATIC,
        VOID,
        CLAIMABLE
    }

    enum GasMode {
        VOID,
        CLAIMABLE
    }

    // configure
    function configureContract(address contractAddress, YieldMode _yield, GasMode gasMode, address governor) external;
    function configure(YieldMode _yield, GasMode gasMode, address governor) external;

    // base configuration options
    function configureClaimableYield() external;
    function configureClaimableYieldOnBehalf(address contractAddress) external;
    function configureAutomaticYield() external;
    function configureAutomaticYieldOnBehalf(address contractAddress) external;
    function configureVoidYield() external;
    function configureVoidYieldOnBehalf(address contractAddress) external;
    function configureClaimableGas() external;
    function configureClaimableGasOnBehalf(address contractAddress) external;
    function configureVoidGas() external;
    function configureVoidGasOnBehalf(address contractAddress) external;
    function configureGovernor(address _governor) external;
    function configureGovernorOnBehalf(address _newGovernor, address contractAddress) external;

    // claim yield
    function claimYield(address contractAddress, address recipientOfYield, uint256 amount) external returns (uint256);
    function claimAllYield(address contractAddress, address recipientOfYield) external returns (uint256);

    // claim gas
    function claimAllGas(address contractAddress, address recipientOfGas) external returns (uint256);
    function claimGasAtMinClaimRate(address contractAddress, address recipientOfGas, uint256 minClaimRateBips)
        external
        returns (uint256);
    function claimMaxGas(address contractAddress, address recipientOfGas) external returns (uint256);
    function claimGas(address contractAddress, address recipientOfGas, uint256 gasToClaim, uint256 gasSecondsToConsume)
        external
        returns (uint256);

    // read functions
    function readClaimableYield(address contractAddress) external view returns (uint256);
    function readYieldConfiguration(address contractAddress) external view returns (uint8);
    function readGasParams(address contractAddress)
        external
        view
        returns (uint256 etherSeconds, uint256 etherBalance, uint256 lastUpdated, GasMode);
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Pool state that can change
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolState {
  /**
   * @notice The globalState structure in the pool stores many values but requires only one slot
   * and is exposed as a single method to save gas when accessed externally.
   * @return price The current price of the pool as a sqrt(token1/token0) Q64.96 value;
   * Returns tick The current tick of the pool, i.e. according to the last tick transition that was run;
   * Returns This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(price) if the price is on a tick
   * boundary;
   * Returns fee The last pool fee value in hundredths of a bip, i.e. 1e-6;
   * Returns timepointIndex The index of the last written timepoint;
   * Returns communityFeeToken0 The community fee percentage of the swap fee in thousandths (1e-3) for token0;
   * Returns communityFeeToken1 The community fee percentage of the swap fee in thousandths (1e-3) for token1;
   * Returns unlocked Whether the pool is currently locked to reentrancy;
   */
  function globalState()
    external
    view
    returns (
      uint160 price,
      int24 tick,
      uint16 fee,
      uint16 timepointIndex,
      uint8 communityFeeToken0,
      uint8 communityFeeToken1,
      bool unlocked
    );

  /**
   * @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool
   * @dev This value can overflow the uint256
   */
  function totalFeeGrowth0Token() external view returns (uint256);

  /**
   * @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool
   * @dev This value can overflow the uint256
   */
  function totalFeeGrowth1Token() external view returns (uint256);

  /**
   * @notice The currently in range liquidity available to the pool
   * @dev This value has no relationship to the total liquidity across all ticks.
   * Returned value cannot exceed type(uint128).max
   */
  function liquidity() external view returns (uint128);

  /**
   * @notice Look up information about a specific tick in the pool
   * @dev This is a public structure, so the `return` natspec tags are omitted.
   * @param tick The tick to look up
   * @return liquidityTotal the total amount of position liquidity that uses the pool either as tick lower or
   * tick upper;
   * Returns liquidityDelta how much liquidity changes when the pool price crosses the tick;
   * Returns outerFeeGrowth0Token the fee growth on the other side of the tick from the current tick in token0;
   * Returns outerFeeGrowth1Token the fee growth on the other side of the tick from the current tick in token1;
   * Returns outerTickCumulative the cumulative tick value on the other side of the tick from the current tick;
   * Returns outerSecondsPerLiquidity the seconds spent per liquidity on the other side of the tick from the current tick;
   * Returns outerSecondsSpent the seconds spent on the other side of the tick from the current tick;
   * Returns initialized Set to true if the tick is initialized, i.e. liquidityTotal is greater than 0
   * otherwise equal to false. Outside values can only be used if the tick is initialized.
   * In addition, these values are only relative and must be used only in comparison to previous snapshots for
   * a specific position.
   */
  function ticks(int24 tick)
    external
    view
    returns (
      uint128 liquidityTotal,
      int128 liquidityDelta,
      uint256 outerFeeGrowth0Token,
      uint256 outerFeeGrowth1Token,
      int56 outerTickCumulative,
      uint160 outerSecondsPerLiquidity,
      uint32 outerSecondsSpent,
      bool initialized
    );

  /** @notice Returns 256 packed tick initialized boolean values. See TickTable for more information */
  function tickTable(int16 wordPosition) external view returns (uint256);

  /**
   * @notice Returns the information about a position by the position's key
   * @dev This is a public mapping of structures, so the `return` natspec tags are omitted.
   * @param key The position's key is a hash of a preimage composed by the owner, bottomTick and topTick
   * @return liquidityAmount The amount of liquidity in the position;
   * Returns lastLiquidityAddTimestamp Timestamp of last adding of liquidity;
   * Returns innerFeeGrowth0Token Fee growth of token0 inside the tick range as of the last mint/burn/poke;
   * Returns innerFeeGrowth1Token Fee growth of token1 inside the tick range as of the last mint/burn/poke;
   * Returns fees0 The computed amount of token0 owed to the position as of the last mint/burn/poke;
   * Returns fees1 The computed amount of token1 owed to the position as of the last mint/burn/poke
   */
  function positions(bytes32 key)
    external
    view
    returns (
      uint128 liquidityAmount,
      uint32 lastLiquidityAddTimestamp,
      uint256 innerFeeGrowth0Token,
      uint256 innerFeeGrowth1Token,
      uint128 fees0,
      uint128 fees1
    );

  /**
   * @notice Returns data about a specific timepoint index
   * @param index The element of the timepoints array to fetch
   * @dev You most likely want to use #getTimepoints() instead of this method to get an timepoint as of some amount of time
   * ago, rather than at a specific index in the array.
   * This is a public mapping of structures, so the `return` natspec tags are omitted.
   * @return initialized whether the timepoint has been initialized and the values are safe to use;
   * Returns blockTimestamp The timestamp of the timepoint;
   * Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the timepoint timestamp;
   * Returns secondsPerLiquidityCumulative the seconds per in range liquidity for the life of the pool as of the timepoint timestamp;
   * Returns volatilityCumulative Cumulative standard deviation for the life of the pool as of the timepoint timestamp;
   * Returns averageTick Time-weighted average tick;
   * Returns volumePerLiquidityCumulative Cumulative swap volume per liquidity for the life of the pool as of the timepoint timestamp;
   */
  function timepoints(uint256 index)
    external
    view
    returns (
      bool initialized,
      uint32 blockTimestamp,
      int56 tickCumulative,
      uint160 secondsPerLiquidityCumulative,
      uint88 volatilityCumulative,
      int24 averageTick,
      uint144 volumePerLiquidityCumulative
    );

  /**
   * @notice Returns the information about active incentive
   * @dev if there is no active incentive at the moment, virtualPool,endTimestamp,startTimestamp would be equal to 0
   * @return virtualPool The address of a virtual pool associated with the current active incentive
   */
  function activeIncentive() external view returns (address virtualPool);

  /**
   * @notice Returns the lock time for added liquidity
   */
  function liquidityCooldown() external view returns (uint32 cooldownInSeconds);

  /**
   * @notice The pool tick spacing
   * @dev Ticks can only be used at multiples of this value
   * e.g.: a tickSpacing of 60 means ticks can be initialized every 60th tick, i.e., ..., -120, -60, 0, 60, 120, ...
   * This value is an int24 to avoid casting even though it is always positive.
   * @return The tick spacing
   */
  function tickSpacing() external view returns (int24);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface ISteerToken {
    function balanceOf(address account) external view returns (uint256);

    function transfer(address dst, uint256 rawAmount) external returns (bool);

    function delegate(address delegatee) external;
}

interface ITreasuryVester {
    function setRecipient(address _recipient) external;

    function claim() external;

    function cutOffFunds() external;

    function getVotingPowerForVestedTokens() external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/Clones.sol)

pragma solidity ^0.8.0;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 *
 * _Available since v3.4._
 */
library ClonesUpgradeable {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            instance := create(0, ptr, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            instance := create2(0, ptr, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000)
            mstore(add(ptr, 0x38), shl(0x60, deployer))
            mstore(add(ptr, 0x4c), salt)
            mstore(add(ptr, 0x6c), keccak256(ptr, 0x37))
            predicted := keccak256(add(ptr, 0x37), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(address implementation, bytes32 salt)
        internal
        view
        returns (address predicted)
    {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

interface IBlastPoints {
	function configurePointsOperator(address operator) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/IGovernor.sol)

pragma solidity ^0.8.0;

import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Interface of the {Governor} core.
 *
 * _Available since v4.3._
 */
abstract contract IGovernorUpgradeable is Initializable, IERC165Upgradeable {
    function __IGovernor_init() internal onlyInitializing {
        __IGovernor_init_unchained();
    }

    function __IGovernor_init_unchained() internal onlyInitializing {
    }
    enum ProposalState {
        Pending,
        Active,
        Canceled,
        Defeated,
        Succeeded,
        Queued,
        Expired,
        Executed
    }

    /**
     * @dev Emitted when a proposal is created.
     */
    event ProposalCreated(
        uint256 proposalId,
        address proposer,
        address[] targets,
        uint256[] values,
        string[] signatures,
        bytes[] calldatas,
        uint256 startBlock,
        uint256 endBlock,
        string description
    );

    /**
     * @dev Emitted when a proposal is canceled.
     */
    event ProposalCanceled(uint256 proposalId);

    /**
     * @dev Emitted when a proposal is executed.
     */
    event ProposalExecuted(uint256 proposalId);

    /**
     * @dev Emitted when a vote is cast.
     *
     * Note: `support` values should be seen as buckets. There interpretation depends on the voting module used.
     */
    event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason);

    /**
     * @notice module:core
     * @dev Name of the governor instance (used in building the ERC712 domain separator).
     */
    function name() public view virtual returns (string memory);

    /**
     * @notice module:core
     * @dev Version of the governor instance (used in building the ERC712 domain separator). Default: "1"
     */
    function version() public view virtual returns (string memory);

    /**
     * @notice module:voting
     * @dev A description of the possible `support` values for {castVote} and the way these votes are counted, meant to
     * be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of
     * key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`.
     *
     * There are 2 standard keys: `support` and `quorum`.
     *
     * - `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`.
     * - `quorum=bravo` means that only For votes are counted towards quorum.
     * - `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum.
     *
     * NOTE: The string can be decoded by the standard
     * https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams[`URLSearchParams`]
     * JavaScript class.
     */
    // solhint-disable-next-line func-name-mixedcase
    function COUNTING_MODE() public pure virtual returns (string memory);

    /**
     * @notice module:core
     * @dev Hashing function used to (re)build the proposal id from the proposal details..
     */
    function hashProposal(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public pure virtual returns (uint256);

    /**
     * @notice module:core
     * @dev Current state of a proposal, following Compound's convention
     */
    function state(uint256 proposalId) public view virtual returns (ProposalState);

    /**
     * @notice module:core
     * @dev Block number used to retrieve user's votes and quorum. As per Compound's Comp and OpenZeppelin's
     * ERC20Votes, the snapshot is performed at the end of this block. Hence, voting for this proposal starts at the
     * beginning of the following block.
     */
    function proposalSnapshot(uint256 proposalId) public view virtual returns (uint256);

    /**
     * @notice module:core
     * @dev Block number at which votes close. Votes close at the end of this block, so it is possible to cast a vote
     * during this block.
     */
    function proposalDeadline(uint256 proposalId) public view virtual returns (uint256);

    /**
     * @notice module:user-config
     * @dev Delay, in number of block, between the proposal is created and the vote starts. This can be increassed to
     * leave time for users to buy voting power, of delegate it, before the voting of a proposal starts.
     */
    function votingDelay() public view virtual returns (uint256);

    /**
     * @notice module:user-config
     * @dev Delay, in number of blocks, between the vote start and vote ends.
     *
     * NOTE: The {votingDelay} can delay the start of the vote. This must be considered when setting the voting
     * duration compared to the voting delay.
     */
    function votingPeriod() public view virtual returns (uint256);

    /**
     * @notice module:user-config
     * @dev Minimum number of cast voted required for a proposal to be successful.
     *
     * Note: The `blockNumber` parameter corresponds to the snaphot used for counting vote. This allows to scale the
     * quroum depending on values such as the totalSupply of a token at this block (see {ERC20Votes}).
     */
    function quorum(uint256 blockNumber) public view virtual returns (uint256);

    /**
     * @notice module:reputation
     * @dev Voting power of an `account` at a specific `blockNumber`.
     *
     * Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or
     * multiple), {ERC20Votes} tokens.
     */
    function getVotes(address account, uint256 blockNumber) public view virtual returns (uint256);

    /**
     * @notice module:voting
     * @dev Returns weither `account` has cast a vote on `proposalId`.
     */
    function hasVoted(uint256 proposalId, address account) public view virtual returns (bool);

    /**
     * @dev Create a new proposal. Vote start {IGovernor-votingDelay} blocks after the proposal is created and ends
     * {IGovernor-votingPeriod} blocks after the voting starts.
     *
     * Emits a {ProposalCreated} event.
     */
    function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    ) public virtual returns (uint256 proposalId);

    /**
     * @dev Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the
     * deadline to be reached.
     *
     * Emits a {ProposalExecuted} event.
     *
     * Note: some module can modify the requirements for execution, for example by adding an additional timelock.
     */
    function execute(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public payable virtual returns (uint256 proposalId);

    /**
     * @dev Cast a vote
     *
     * Emits a {VoteCast} event.
     */
    function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256 balance);

    /**
     * @dev Cast a with a reason
     *
     * Emits a {VoteCast} event.
     */
    function castVoteWithReason(
        uint256 proposalId,
        uint8 support,
        string calldata reason
    ) public virtual returns (uint256 balance);

    /**
     * @dev Cast a vote using the user cryptographic signature.
     *
     * Emits a {VoteCast} event.
     */
    function castVoteBySig(
        uint256 proposalId,
        uint8 support,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual returns (uint256 balance);
    uint256[50] private __gap;
}

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

interface IWhitelistRegistry {
    event PermissionsAdded(
        address whitelistManager,
        address vault,
        address[] addressesAdded
    );
    event PermissionsRemoved(
        address whitelistManager,
        address vault,
        address[] addressesRemoved
    );
    event ManagerAdded(address vaultAddress, address manager);

    function addPermissions(
        address _vaultAddress,
        address[] calldata _addresses
    ) external;

    function registerWhitelistManager(address manager) external;

    function revokePermissions(
        address _vaultAddress,
        address[] calldata _addresses
    ) external;
}

File 128 of 177 : IBeaconUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.0;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeaconUpgradeable {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {BeaconProxy} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissionless pool actions
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces
interface IAlgebraPoolActions {
  /**
   * @notice Sets the initial price for the pool
   * @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
   * @param price the initial sqrt price of the pool as a Q64.96
   */
  function initialize(uint160 price) external;

  /**
   * @notice Adds liquidity for the given recipient/bottomTick/topTick position
   * @dev The caller of this method receives a callback in the form of IAlgebraMintCallback# AlgebraMintCallback
   * in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
   * on bottomTick, topTick, the amount of liquidity, and the current price.
   * @param sender The address which will receive potential surplus of paid tokens
   * @param recipient The address for which the liquidity will be created
   * @param bottomTick The lower tick of the position in which to add liquidity
   * @param topTick The upper tick of the position in which to add liquidity
   * @param amount The desired amount of liquidity to mint
   * @param data Any data that should be passed through to the callback
   * @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
   * @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
   * @return liquidityActual The actual minted amount of liquidity
   */
  function mint(
    address sender,
    address recipient,
    int24 bottomTick,
    int24 topTick,
    uint128 amount,
    bytes calldata data
  )
    external
    returns (
      uint256 amount0,
      uint256 amount1,
      uint128 liquidityActual
    );

  /**
   * @notice Collects tokens owed to a position
   * @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
   * Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
   * amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
   * actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
   * @param recipient The address which should receive the fees collected
   * @param bottomTick The lower tick of the position for which to collect fees
   * @param topTick The upper tick of the position for which to collect fees
   * @param amount0Requested How much token0 should be withdrawn from the fees owed
   * @param amount1Requested How much token1 should be withdrawn from the fees owed
   * @return amount0 The amount of fees collected in token0
   * @return amount1 The amount of fees collected in token1
   */
  function collect(
    address recipient,
    int24 bottomTick,
    int24 topTick,
    uint128 amount0Requested,
    uint128 amount1Requested
  ) external returns (uint128 amount0, uint128 amount1);

  /**
   * @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
   * @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
   * @dev Fees must be collected separately via a call to #collect
   * @param bottomTick The lower tick of the position for which to burn liquidity
   * @param topTick The upper tick of the position for which to burn liquidity
   * @param amount How much liquidity to burn
   * @return amount0 The amount of token0 sent to the recipient
   * @return amount1 The amount of token1 sent to the recipient
   */
  function burn(
    int24 bottomTick,
    int24 topTick,
    uint128 amount
  ) external returns (uint256 amount0, uint256 amount1);

  /**
   * @notice Swap token0 for token1, or token1 for token0
   * @dev The caller of this method receives a callback in the form of IAlgebraSwapCallback# AlgebraSwapCallback
   * @param recipient The address to receive the output of the swap
   * @param zeroToOne The direction of the swap, true for token0 to token1, false for token1 to token0
   * @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
   * @param limitSqrtPrice The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
   * value after the swap. If one for zero, the price cannot be greater than this value after the swap
   * @param data Any data to be passed through to the callback. If using the Router it should contain
   * SwapRouter#SwapCallbackData
   * @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
   * @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
   */
  function swap(
    address recipient,
    bool zeroToOne,
    int256 amountSpecified,
    uint160 limitSqrtPrice,
    bytes calldata data
  ) external returns (int256 amount0, int256 amount1);

  /**
   * @notice Swap token0 for token1, or token1 for token0 (tokens that have fee on transfer)
   * @dev The caller of this method receives a callback in the form of I AlgebraSwapCallback# AlgebraSwapCallback
   * @param sender The address called this function (Comes from the Router)
   * @param recipient The address to receive the output of the swap
   * @param zeroToOne The direction of the swap, true for token0 to token1, false for token1 to token0
   * @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
   * @param limitSqrtPrice The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
   * value after the swap. If one for zero, the price cannot be greater than this value after the swap
   * @param data Any data to be passed through to the callback. If using the Router it should contain
   * SwapRouter#SwapCallbackData
   * @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
   * @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
   */
  function swapSupportingFeeOnInputTokens(
    address sender,
    address recipient,
    bool zeroToOne,
    int256 amountSpecified,
    uint160 limitSqrtPrice,
    bytes calldata data
  ) external returns (int256 amount0, int256 amount1);

  /**
   * @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
   * @dev The caller of this method receives a callback in the form of IAlgebraFlashCallback# AlgebraFlashCallback
   * @dev All excess tokens paid in the callback are distributed to liquidity providers as an additional fee. So this method can be used
   * to donate underlying tokens to currently in-range liquidity providers by calling with 0 amount{0,1} and sending
   * the donation amount(s) from the callback
   * @param recipient The address which will receive the token0 and token1 amounts
   * @param amount0 The amount of token0 to send
   * @param amount1 The amount of token1 to send
   * @param data Any data to be passed through to the callback
   */
  function flash(
    address recipient,
    uint256 amount0,
    uint256 amount1,
    bytes calldata data
  ) external;
}

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

pragma solidity ^0.8.0;

import "../ERC721Upgradeable.sol";
import "./IERC721EnumerableUpgradeable.sol";
import "../../../proxy/utils/Initializable.sol";

/**
 * @dev This implements an optional extension of {ERC721} defined in the EIP that adds
 * enumerability of all the token ids in the contract as well as all token ids owned by each
 * account.
 */
abstract contract ERC721EnumerableUpgradeable is Initializable, ERC721Upgradeable, IERC721EnumerableUpgradeable {
    function __ERC721Enumerable_init() internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __ERC721Enumerable_init_unchained();
    }

    function __ERC721Enumerable_init_unchained() internal onlyInitializing {
    }
    // Mapping from owner to list of owned token IDs
    mapping(address => mapping(uint256 => uint256)) private _ownedTokens;

    // Mapping from token ID to index of the owner tokens list
    mapping(uint256 => uint256) private _ownedTokensIndex;

    // Array with all token ids, used for enumeration
    uint256[] private _allTokens;

    // Mapping from token id to position in the allTokens array
    mapping(uint256 => uint256) private _allTokensIndex;

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

    /**
     * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721Upgradeable.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
        return _ownedTokens[owner][index];
    }

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

    /**
     * @dev See {IERC721Enumerable-tokenByIndex}.
     */
    function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721EnumerableUpgradeable.totalSupply(), "ERC721Enumerable: global index out of bounds");
        return _allTokens[index];
    }

    /**
     * @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` cannot be the zero address.
     * - `to` cannot be the zero address.
     *
     * 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 override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            _addTokenToAllTokensEnumeration(tokenId);
        } else if (from != to) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }
        if (to == address(0)) {
            _removeTokenFromAllTokensEnumeration(tokenId);
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }

    /**
     * @dev Private function to add a token to this extension's ownership-tracking data structures.
     * @param to address representing the new owner of the given token ID
     * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        uint256 length = ERC721Upgradeable.balanceOf(to);
        _ownedTokens[to][length] = tokenId;
        _ownedTokensIndex[tokenId] = length;
    }

    /**
     * @dev Private function to add a token to this extension's token tracking data structures.
     * @param tokenId uint256 ID of the token to be added to the tokens list
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

    /**
     * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
     * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
     * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
     * This has O(1) time complexity, but alters the order of the _ownedTokens array.
     * @param from address representing the previous owner of the given token ID
     * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = ERC721Upgradeable.balanceOf(from) - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }

    /**
     * @dev Private function to remove a token from this extension's token tracking data structures.
     * This has O(1) time complexity, but alters the order of the _allTokens array.
     * @param tokenId uint256 ID of the token to be removed from the tokens list
     */
    function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
        // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = _allTokens.length - 1;
        uint256 tokenIndex = _allTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
        // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
        // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete _allTokensIndex[tokenId];
        _allTokens.pop();
    }
    uint256[46] private __gap;
}

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

pragma solidity ^0.8.0;

import "../ERC1967/ERC1967UpgradeUpgradeable.sol";
import "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 *
 * _Available since v4.1._
 */
abstract contract UUPSUpgradeable is Initializable, ERC1967UpgradeUpgradeable {
    function __UUPSUpgradeable_init() internal onlyInitializing {
        __ERC1967Upgrade_init_unchained();
        __UUPSUpgradeable_init_unchained();
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
    address private immutable __self = address(this);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        require(address(this) != __self, "Function must be called through delegatecall");
        require(_getImplementation() == __self, "Function must be called through active proxy");
        _;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     */
    function upgradeTo(address newImplementation) external virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, new bytes(0), false);
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, data, true);
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeTo} and {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal override onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;
    uint256[50] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/extensions/GovernorTimelockControl.sol)

pragma solidity ^0.8.0;

import "./IGovernorTimelockUpgradeable.sol";
import "../GovernorUpgradeable.sol";
import "../TimelockControllerUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";

/**
 * @dev Extension of {Governor} that binds the execution process to an instance of {TimelockController}. This adds a
 * delay, enforced by the {TimelockController} to all successful proposal (in addition to the voting duration). The
 * {Governor} needs the proposer (an ideally the executor) roles for the {Governor} to work properly.
 *
 * Using this model means the proposal will be operated by the {TimelockController} and not by the {Governor}. Thus,
 * the assets and permissions must be attached to the {TimelockController}. Any asset sent to the {Governor} will be
 * inaccessible.
 *
 * _Available since v4.3._
 */
abstract contract GovernorTimelockControlUpgradeable is Initializable, IGovernorTimelockUpgradeable, GovernorUpgradeable {
    TimelockControllerUpgradeable private _timelock;
    mapping(uint256 => bytes32) private _timelockIds;

    /**
     * @dev Emitted when the timelock controller used for proposal execution is modified.
     */
    event TimelockChange(address oldTimelock, address newTimelock);

    /**
     * @dev Set the timelock.
     */
    function __GovernorTimelockControl_init(TimelockControllerUpgradeable timelockAddress) internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __IGovernor_init_unchained();
        __IGovernorTimelock_init_unchained();
        __GovernorTimelockControl_init_unchained(timelockAddress);
    }

    function __GovernorTimelockControl_init_unchained(TimelockControllerUpgradeable timelockAddress) internal onlyInitializing {
        _updateTimelock(timelockAddress);
    }

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

    /**
     * @dev Overriden version of the {Governor-state} function with added support for the `Queued` status.
     */
    function state(uint256 proposalId) public view virtual override(IGovernorUpgradeable, GovernorUpgradeable) returns (ProposalState) {
        ProposalState status = super.state(proposalId);

        if (status != ProposalState.Succeeded) {
            return status;
        }

        // core tracks execution, so we just have to check if successful proposal have been queued.
        bytes32 queueid = _timelockIds[proposalId];
        if (queueid == bytes32(0)) {
            return status;
        } else if (_timelock.isOperationDone(queueid)) {
            return ProposalState.Executed;
        } else {
            return ProposalState.Queued;
        }
    }

    /**
     * @dev Public accessor to check the address of the timelock
     */
    function timelock() public view virtual override returns (address) {
        return address(_timelock);
    }

    /**
     * @dev Public accessor to check the eta of a queued proposal
     */
    function proposalEta(uint256 proposalId) public view virtual override returns (uint256) {
        uint256 eta = _timelock.getTimestamp(_timelockIds[proposalId]);
        return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value
    }

    /**
     * @dev Function to queue a proposal to the timelock.
     */
    function queue(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public virtual override returns (uint256) {
        uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);

        require(state(proposalId) == ProposalState.Succeeded, "Governor: proposal not successful");

        uint256 delay = _timelock.getMinDelay();
        _timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, descriptionHash);
        _timelock.scheduleBatch(targets, values, calldatas, 0, descriptionHash, delay);

        emit ProposalQueued(proposalId, block.timestamp + delay);

        return proposalId;
    }

    /**
     * @dev Overriden execute function that run the already queued proposal through the timelock.
     */
    function _execute(
        uint256, /* proposalId */
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal virtual override {
        _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, descriptionHash);
    }

    /**
     * @dev Overriden version of the {Governor-_cancel} function to cancel the timelocked proposal if it as already
     * been queued.
     */
    function _cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal virtual override returns (uint256) {
        uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash);

        if (_timelockIds[proposalId] != 0) {
            _timelock.cancel(_timelockIds[proposalId]);
            delete _timelockIds[proposalId];
        }

        return proposalId;
    }

    /**
     * @dev Address through which the governor executes action. In this case, the timelock.
     */
    function _executor() internal view virtual override returns (address) {
        return address(_timelock);
    }

    /**
     * @dev Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates
     * must be proposed, scheduled and executed using the {Governor} workflow.
     */
    function updateTimelock(TimelockControllerUpgradeable newTimelock) external virtual onlyGovernance {
        _updateTimelock(newTimelock);
    }

    function _updateTimelock(TimelockControllerUpgradeable newTimelock) private {
        emit TimelockChange(address(_timelock), address(newTimelock));
        _timelock = newTimelock;
    }
    uint256[48] private __gap;
}

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

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";

import { Beacon } from "../Beacon.sol";

/**
 * @dev contract managing beacon data for all vaults
 */
abstract contract BeaconManager is
    OwnableUpgradeable,
    AccessControlUpgradeable
{
    /// @dev Beacon registeration event
    /// @param _name The name of the beacon getting registered
    /// @param _address The implementation address that this beacon will point to
    /// @param _ipfsHash IPFS hash for the config of this beacon
    event BeaconRegistered(string _name, address _address, string _ipfsHash);

    /// @dev Beacon config updation event
    /// @param _name The name of the beacon getting registered
    /// @param _ipfsHash updated IPFS hash for the config of this beacon
    event BeaconConfigUpdated(string _name, string _ipfsHash);

    /// @dev Beacon deregisteration event
    /// @param _name The name of the beacon getting registered
    event BeaconDeregistered(string _name);

    // Beacon creator, used to create and register new beacons for new vault types
    bytes32 internal constant BEACON_CREATOR = keccak256("BEACON_CREATOR");

    // Mapping beaconName => beacon address. Used to find the beacon for a given vault type.
    mapping(string => address) public beaconAddresses;

    // Mapping address => beaconName. Used to find what vault type a given beacon or vault is.
    // Note that beaconTypes applies to both beacons and vaults.
    mapping(address => string) public beaconTypes;

    /// @dev Registers a beacon associated with a new vault type
    /// @param _name The name of the vault type this beacon will be using
    /// @param _address The address of the beacon contract
    /// @param _ipfsConfigForBeacon IPFS hash for the config of this beacon
    /// @dev This function is only available to the beacon creator
    /// @dev Registers any address as a new beacon. Useful for alternative beacon types (i.e. a contract which will use a proxy structure other than the standard beacon).
    function registerBeacon(
        string calldata _name,
        address _address,
        string memory _ipfsConfigForBeacon
    ) public onlyRole(BEACON_CREATOR) {
        // Ensure no beacon exists with given name, so that this function can't edit an existing beacon address
        require(beaconAddresses[_name] == address(0), "Beacon already exists");

        // Register beacon
        beaconAddresses[_name] = _address;
        beaconTypes[_address] = _name;
        emit BeaconRegistered(_name, _address, _ipfsConfigForBeacon);
    }

    /// @dev Deploy new beacon for a new vault type AND register it
    /// @param _address The address of the implementation for the beacon
    /// @param _name The name of the beacon (identifier)
    /// @param _ipfsConfigForBeacon IPFS hash for the config of this beacon
    /// note that the contract registered as a beacon should not be used as a vault, to avoid confusion.
    function deployAndRegisterBeacon(
        address _address,
        string calldata _name,
        string calldata _ipfsConfigForBeacon
    ) external onlyRole(BEACON_CREATOR) returns (address) {
        // Ensure no beacon exists with given name, so that this function can't edit an existing beacon address
        require(beaconAddresses[_name] == address(0), "Beacon already exists");

        // Deploy new beacon instance
        Beacon newBeacon = new Beacon(_address);

        // Transfer ownership to governance
        newBeacon.transferOwnership(owner());

        // Record beacon address at beacon name, so that new vaults can be created with this beacon by passing in beacon name
        beaconAddresses[_name] = address(newBeacon);
        beaconTypes[address(newBeacon)] = _name;

        emit BeaconRegistered(_name, _address, _ipfsConfigForBeacon);
        return address(newBeacon);
    }

    /// @dev Updates the ipfs link storing the beaconConfig
    /// @param _name The name of the beacon (identifier)
    /// @param _newIPFSConfigForBeacon IPFS hash for the config of this beacon
    function updateBeaconConfig(
        string calldata _name,
        string calldata _newIPFSConfigForBeacon
    ) external onlyRole(BEACON_CREATOR) {
        require(beaconAddresses[_name] != address(0), "Beacon does not exist");
        emit BeaconConfigUpdated(_name, _newIPFSConfigForBeacon);
    }

    /// @dev Removes a beacon associated with a vault type
    /// @param _name The name of the beacon (identifier)
    /// @dev This will stop the creation of more vaults of the type provided
    function deregisterBeacon(string calldata _name)
        external
        onlyRole(BEACON_CREATOR)
    {
        emit BeaconDeregistered(_name);
        delete beaconAddresses[_name];
    }
}

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

import "./WasabeeIntegralBaseLiquidityManager.sol";

contract WasabeeIntegralMultiPositionLiquidityManager is
    WasabeeIntegralBaseLiquidityManager
{
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

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

pragma solidity ^0.8.0;

/**
 * @dev String operations.
 */
library StringsUpgradeable {
    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);
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissionless pool actions
/// @notice Contains pool methods that can be called by anyone
interface IUniswapV3PoolActions {
    /// @notice Sets the initial price for the pool
    /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value
    /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96
    function initialize(uint160 sqrtPriceX96) external;

    /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
    /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
    /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
    /// on tickLower, tickUpper, the amount of liquidity, and the current price.
    /// @param recipient The address for which the liquidity will be created
    /// @param tickLower The lower tick of the position in which to add liquidity
    /// @param tickUpper The upper tick of the position in which to add liquidity
    /// @param amount The amount of liquidity to mint
    /// @param data Any data that should be passed through to the callback
    /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
    /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount,
        bytes calldata data
    ) external returns (uint256 amount0, uint256 amount1);

    /// @notice Collects tokens owed to a position
    /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity.
    /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or
    /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the
    /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.
    /// @param recipient The address which should receive the fees collected
    /// @param tickLower The lower tick of the position for which to collect fees
    /// @param tickUpper The upper tick of the position for which to collect fees
    /// @param amount0Requested How much token0 should be withdrawn from the fees owed
    /// @param amount1Requested How much token1 should be withdrawn from the fees owed
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);

    /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position
    /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0
    /// @dev Fees must be collected separately via a call to #collect
    /// @param tickLower The lower tick of the position for which to burn liquidity
    /// @param tickUpper The upper tick of the position for which to burn liquidity
    /// @param amount How much liquidity to burn
    /// @return amount0 The amount of token0 sent to the recipient
    /// @return amount1 The amount of token1 sent to the recipient
    function burn(
        int24 tickLower,
        int24 tickUpper,
        uint128 amount
    ) external returns (uint256 amount0, uint256 amount1);

    /// @notice Swap token0 for token1, or token1 for token0
    /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
    /// @param recipient The address to receive the output of the swap
    /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
    /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
    /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
    /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
    /// @param data Any data to be passed through to the callback
    /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
    /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);

    /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback
    /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback
    /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling
    /// with 0 amount{0,1} and sending the donation amount(s) from the callback
    /// @param recipient The address which will receive the token0 and token1 amounts
    /// @param amount0 The amount of token0 to send
    /// @param amount1 The amount of token1 to send
    /// @param data Any data to be passed through to the callback
    function flash(
        address recipient,
        uint256 amount0,
        uint256 amount1,
        bytes calldata data
    ) external;

    /// @notice Increase the maximum number of price and liquidity observations that this pool will store
    /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to
    /// the input observationCardinalityNext.
    /// @param observationCardinalityNext The desired minimum number of observations for the pool to store
    function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;
}

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

interface ISteerToken {
    function createSnapshot() external returns (uint256);

    function verifyVote(
        string memory mailTo,
        string memory mailContents,
        bytes memory signature
    ) external returns (address);
}

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

interface IStakingRewards {
    // Structs

    /// stakingToken : Address of the token that will be staked
    /// rewardToken : Address of the token that will be given as reward
    /// rewardRate : Rate at which the rewards will be calculated,
    ///             reward rate will be multiplied by 100 for decimal precision,
    ///             for e.g. 6000 means 60%/year, 1000 means 10%/year
    /// start : Start time of the staking pool
    /// end : Ending time for the staking pool
    struct Pool {
        address stakingToken;
        address rewardToken;
        uint256 rewardRate;
        uint256 totalAmount;
        uint256 start;
        uint256 end;
    }

    ///balance : Staked balance of a user
    ///lastRewarded : The time at which a user was last rewarded
    ///rewards : Amount of rewards accrued by a user(Note - This is not a track of
    /// real time rewards,this is a track of rewards till the last time user interacted with
    /// the last rewarded variable)
    struct UserInfo {
        uint256 balance;
        uint256 lastRewarded;
        uint256 rewards;
    }

    // Events

    event Staked(address indexed user, uint256 amount, uint256 poolId);
    event Withdrawn(address indexed user, uint256 amount, uint256 poolId);
    event RewardPaid(address indexed user, uint256 poolId, uint256 reward);
    event RewardsDeposited(address depositor, uint256 poolId, uint256 amount);
    event RewardsWithdrawn(uint256 amount, uint256 poolId);

    // Functions

    function createPool(
        address stakingToken,
        address rewardToken,
        uint256 rewardRate,
        uint256 start,
        uint256 end
    ) external;

    function stake(uint256 amount, uint256 poolId) external;

    function stakeFor(address user, uint256 amount, uint256 poolId) external;

    function unstake(uint256 poolId) external;

    function depositRewards(uint256 poolId, uint256 amount) external;

    function withdrawRewards(
        uint256 poolId,
        uint256 amount,
        address receiver
    ) external;

    function setJobState(uint256 poolId, bool pause) external;

    function claimPendingRewards(uint256 poolId) external;

    function getRewardsForAPool(
        address account,
        uint256 poolId
    ) external view returns (uint256);

    function getPools()
        external
        view
        returns (Pool[] memory pools, string[] memory symbols);

    function getPool(uint256 poolId) external view returns (Pool memory pool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/compatibility/IGovernorCompatibilityBravo.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Interface extension that adds missing functions to the {Governor} core to provide `GovernorBravo` compatibility.
 *
 * _Available since v4.3._
 */
abstract contract IGovernorCompatibilityBravoUpgradeable is Initializable, IGovernorUpgradeable {
    function __IGovernorCompatibilityBravo_init() internal onlyInitializing {
        __IGovernor_init_unchained();
        __IGovernorCompatibilityBravo_init_unchained();
    }

    function __IGovernorCompatibilityBravo_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Proposal structure from Compound Governor Bravo. Not actually used by the compatibility layer, as
     * {{proposal}} returns a very different structure.
     */
    struct Proposal {
        uint256 id;
        address proposer;
        uint256 eta;
        address[] targets;
        uint256[] values;
        string[] signatures;
        bytes[] calldatas;
        uint256 startBlock;
        uint256 endBlock;
        uint256 forVotes;
        uint256 againstVotes;
        uint256 abstainVotes;
        bool canceled;
        bool executed;
        mapping(address => Receipt) receipts;
    }

    /**
     * @dev Receipt structure from Compound Governor Bravo
     */
    struct Receipt {
        bool hasVoted;
        uint8 support;
        uint96 votes;
    }

    /**
     * @dev Part of the Governor Bravo's interface.
     */
    function quorumVotes() public view virtual returns (uint256);

    /**
     * @dev Part of the Governor Bravo's interface: _"The official record of all proposals ever proposed"_.
     */
    function proposals(uint256)
        public
        view
        virtual
        returns (
            uint256 id,
            address proposer,
            uint256 eta,
            uint256 startBlock,
            uint256 endBlock,
            uint256 forVotes,
            uint256 againstVotes,
            uint256 abstainVotes,
            bool canceled,
            bool executed
        );

    /**
     * @dev Part of the Governor Bravo's interface: _"Function used to propose a new proposal"_.
     */
    function propose(
        address[] memory targets,
        uint256[] memory values,
        string[] memory signatures,
        bytes[] memory calldatas,
        string memory description
    ) public virtual returns (uint256);

    /**
     * @dev Part of the Governor Bravo's interface: _"Queues a proposal of state succeeded"_.
     */
    function queue(uint256 proposalId) public virtual;

    /**
     * @dev Part of the Governor Bravo's interface: _"Executes a queued proposal if eta has passed"_.
     */
    function execute(uint256 proposalId) public payable virtual;

    /**
     * @dev Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold.
     */
    function cancel(uint256 proposalId) public virtual;

    /**
     * @dev Part of the Governor Bravo's interface: _"Gets actions of a proposal"_.
     */
    function getActions(uint256 proposalId)
        public
        view
        virtual
        returns (
            address[] memory targets,
            uint256[] memory values,
            string[] memory signatures,
            bytes[] memory calldatas
        );

    /**
     * @dev Part of the Governor Bravo's interface: _"Gets the receipt for a voter on a given proposal"_.
     */
    function getReceipt(uint256 proposalId, address voter) public view virtual returns (Receipt memory);
    uint256[50] private __gap;
}

File 140 of 177 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/utils/Initializable.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since 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.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
 * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() initializer {}
 * ```
 * ====
 */
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() {
        // If the contract is initializing we ignore whether _initialized is set in order to support multiple
        // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
        // contract may have been reentered.
        require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");

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

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }

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

    function _isConstructor() private view returns (bool) {
        return !AddressUpgradeable.isContract(address(this));
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.4 <0.9.0;

import '../interfaces/pool/IAlgebraPoolErrors.sol';

/// @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
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries
library TickMath {
  /// @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 price 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 price) {
    unchecked {
      // get abs value
      int24 absTickMask = tick >> (24 - 1);
      uint256 absTick = uint24((tick + absTickMask) ^ absTickMask);
      if (absTick > uint24(MAX_TICK)) revert IAlgebraPoolErrors.tickOutOfRange();

      uint256 ratio = 0x100000000000000000000000000000000;
      if (absTick & 0x1 != 0) ratio = 0xfffcb933bd6fad37aa2d162d1a594001;
      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) {
        if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
        if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
      }

      if (tick > 0) {
        assembly {
          ratio := div(not(0), 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
      price = uint160((ratio + 0xFFFFFFFF) >> 32);
    }
  }

  /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
  /// @dev Throws in case price < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
  /// ever return.
  /// @param price 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 price) internal pure returns (int24 tick) {
    unchecked {
      // second inequality must be >= because the price can never reach the price at the max tick
      if (price < MIN_SQRT_RATIO || price >= MAX_SQRT_RATIO) revert IAlgebraPoolErrors.priceOutOfRange();
      uint256 ratio = uint256(price) << 32;

      uint256 r = ratio;
      uint256 msb;

      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) <= price ? tickHi : tickLow;
    }
  }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)

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 onlyInitializing {
        __Context_init_unchained();
        __Pausable_init_unchained();
    }

    function __Pausable_init_unchained() internal onlyInitializing {
        _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;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface IKeeperRegistry {
    enum permissionType {
        NONE,
        FULL,
        SLASHED
    }

    /**
     * Any given address can be in one of three different states:
        1. Not a keeper.
        2. A former keeper who is queued to leave, i.e. they no longer have a keeper license but still have some funds locked in the contract. 
        3. A current keeper.
     * Keepers can themselves each be in one of two states:
        1. In good standing. This is signified by bondHeld >= bondAmount.
        2. Not in good standing. 
        If a keepers is not in good standing, they retain their license and ability to vote, but any slash will remove their privileges.
     * The only way for a keeper's bondHeld to drop to 0 is for them to leave or be slashed. Either way they lose their license in the process.
     */
    struct WorkerDetails {
        uint256 bondHeld; // bondCoin held by this keeper.
        uint256 licenseNumber; // Index of this keeper in the license mapping, i.e. which license they own. If they don't own a license, this will be 0.
        uint256 leaveTimestamp; // If this keeper has queued to leave, they can withdraw their bond after this date.
    }

    event PermissionChanged(
        address indexed _subject,
        permissionType indexed _permissionType
    );
    event LeaveQueued(address indexed keeper, uint256 leaveTimestamp);

    /**
     * @param coinAddress the address of the ERC20 which will be used for bonds; intended to be Steer token.
     * @param keeperTransferDelay the amount of time (in seconds) between when a keeper relinquishes their license and when they can
            withdraw their funds. Intended to be 2 weeks - 1 month.
     */
    function initialize(
        address coinAddress,
        uint256 keeperTransferDelay,
        uint256 maxKeepers,
        uint256 bondSize
    ) external;

    function maxNumKeepers() external view returns (uint256);

    function currentNumKeepers() external view returns (uint256);

    /**
     * @dev setup utility function for owner to add initial keepers. Addresses must each be unique and not hold any bondToken.
     * @param joiners array of addresses to become keepers.
     * note that this function will pull bondToken from the owner equal to bondAmount * numJoiners.
     */
    function joiningForOwner(address[] calldata joiners) external;

    /**
     * @param amount Amount of bondCoin to be deposited.
     * @dev this function has three uses:
        1. If the caller is a keeper, they can increase their bondHeld by amount.
        2. If the caller is not a keeper or former keeper, they can attempt to claim a keeper license and become a keeper.
        3. If the caller is a former keeper, they can attempt to cancel their leave request, claim a keeper license, and become a keeper.
        In all 3 cases registry[msg.sender].bondHeld is increased by amount. In the latter 2, msg.sender's bondHeld after the transaction must be >= bondAmount.
     */
    function join(uint256 licenseNumber, uint256 amount) external;

    function queueToLeave() external;

    function leave() external;

    /**
     * @dev returns true if the given address has the power to vote, false otherwise. The address has the power to vote if it is within the keeper array.
     */
    function checkLicense(address targetAddress)
        external
        view
        returns (uint256);

    /**
     * @dev slashes a keeper, removing their permissions and forfeiting their bond.
     * @param targetKeeper keeper to denounce
     * @param amount amount of bondCoin to slash
     */
    function denounce(address targetKeeper, uint256 amount) external;

    /**
     * @dev withdraws slashed tokens from the vault and sends them to targetAddress.
     * @param amount amount of bondCoin to withdraw
     * @param targetAddress address receiving the tokens
     */
    function withdrawFreeCoin(uint256 amount, address targetAddress) external;

    /**
     * @dev change bondAmount to a new value.
     * @dev implicitly changes keeper permissions. If the bondAmount is being increased, existing keepers will not be slashed or removed. 
            note, they will still be able to vote until they are slashed.
     * @param amount new bondAmount.
     */
    function changeBondAmount(uint256 amount) external;

    /**
     * @dev change numKeepers to a new value. If numKeepers is being reduced, this will not remove any keepers, nor will it change orchestrator requirements.
        However, it will render keeper licenses > maxNumKeepers invalid and their votes will stop counting.
     */
    function changeMaxKeepers(uint16 newNumKeepers) external;
}

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

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");
        }
    }
}

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

import "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract Timelock is
    Initializable,
    TimelockControllerUpgradeable,
    OwnableUpgradeable,
    UUPSUpgradeable
{
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    function initialize(
        uint256 minDelay,
        address[] memory proposers,
        address[] memory executors
    ) public initializer {
        __TimelockController_init(minDelay, proposers, executors);
        __Ownable_init();
        __UUPSUpgradeable_init();
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (governance/extensions/IGovernorTimelock.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Extension of the {IGovernor} for timelock supporting modules.
 *
 * _Available since v4.3._
 */
abstract contract IGovernorTimelockUpgradeable is Initializable, IGovernorUpgradeable {
    function __IGovernorTimelock_init() internal onlyInitializing {
        __IGovernor_init_unchained();
        __IGovernorTimelock_init_unchained();
    }

    function __IGovernorTimelock_init_unchained() internal onlyInitializing {
    }
    event ProposalQueued(uint256 proposalId, uint256 eta);

    function timelock() public view virtual returns (address);

    function proposalEta(uint256 proposalId) public view virtual returns (uint256);

    function queue(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public virtual returns (uint256 proposalId);
    uint256[50] private __gap;
}

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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165Upgradeable {
    /**
     * @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);
}

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

import "./interfaces/IStrategyRegistry.sol";

// Factory
import "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol";

// Proxy Support
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";

// Vault support
import "./interfaces/IVaultRegistry.sol";
import "./interfaces/IImplementation.sol";

// Beacon support
import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";

// Governance
import { IOrchestrator } from "./interfaces/IOrchestrator.sol";

// Inheritance
import { InterfaceManager } from "./VaultRegistry/InterfaceManager.sol";
import { IFeeManager } from "./interfaces/IFeeManager.sol";
import { IMultiPositionManager } from "./interfaces/IMultiPositionManager.sol";

/// @title A registry for vaults
/// @author Steer Protocol
/// @dev All vaults are created through this contract
contract VaultRegistry is
    Initializable,
    UUPSUpgradeable,
    PausableUpgradeable,
    InterfaceManager
{
    /// @dev Vault creation event
    /// @param deployer The address of the deployer
    /// @param vault The address of the vault
    /// @param tokenId ERC721 token id for the vault
    /// @param vaultManager is the address which will manage the vault being created
    event VaultCreated(
        address deployer,
        address vault,
        string beaconName,
        uint256 indexed tokenId,
        address vaultManager
    );

    /// @dev Vault state change event
    /// @param vault The address of the vault
    /// @param newState The new state of the vault
    event VaultStateChanged(address indexed vault, VaultState newState);

    /// @dev Fee setting in FeeManager failed
    event FeeSettingFailed(address, string);

    /**
     * @dev all necessary data for vault. Name and symbol are stored in vault's ERC20. Owner is stored with tokenId in StrategyRegistry.
     * tokenId: NFT identifier number
     * vaultAddress: address of vault this describes
     * state: state of the vault.
     */
    struct VaultData {
        VaultState state;
        uint256 tokenId; //NFT ownership of this vault and all others that use vault's exec bundle
        uint256 vaultID; //unique identifier for this vault and strategy token id
        string payloadIpfs;
        address vaultAddress;
        string beaconName;
    }

    /**
     * PendingApproval: strategy is submitted but has not yet been approved by the owner
     * PendingThreshold: strategy is approved but has not yet reached the threshold of TVL required
     * Paused: strategy was active but something went wrong, so now it's paused
     * Active: strategy is active and can be used
     * Retired: strategy is retired and can no longer be used
     */
    enum VaultState {
        PendingApproval,
        PendingThreshold,
        Paused,
        Active,
        Retired
    }

    // Pause role for disabling vault creation in the event of an emergency
    bytes32 internal constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    // Governance role for controlling aspects of the registry
    bytes32 internal constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");

    // Total vault count
    uint256 public totalVaultCount;

    // Mapping for vaults to their details
    // Vault Address => VaultDetails
    mapping(address => VaultData) internal vaults;

    // Mapping from strategy token ID (Execution Bundle) to list of linked vault IDs
    //  Strategy ID => (VaultId => vault address)
    mapping(uint256 => mapping(uint256 => address)) public linkedVaults;

    // Mapping for strategy token ID to number of vaults created using that strategy.
    mapping(uint256 => uint256) internal linkedVaultCounts;

    //Orchestrator address
    address public orchestrator;

    // Strategy registry address--used for strategy IDs
    IStrategyRegistry public strategyRegistry;

    // Misc addresses--used to point vaults towards the correct contracts.
    address public whitelistRegistry;
    address public feeManager;
    address public vaultHelper;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    /// @dev intializes the vault registry
    /// @param _orchestrator The address of the orchestrator
    /// @param _strategyRegistry The address of the strategy registry
    /// @param _whitelistRegistry Address of whitelist registry which keeps track of whitelist managers and members of whitelisted vaults
    function initialize(
        address _orchestrator,
        address _strategyRegistry,
        address _whitelistRegistry
    ) public initializer {
        __UUPSUpgradeable_init();
        __Ownable_init();
        __AccessControl_init();
        __Pausable_init();

        require(_strategyRegistry != address(0), "address(0)");
        require(_whitelistRegistry != address(0), "address(0)");
        require(_orchestrator != address(0), "address(0)");
        orchestrator = _orchestrator;
        // Instantiate the strategy registry
        strategyRegistry = IStrategyRegistry(_strategyRegistry);

        // Record misc addresses
        whitelistRegistry = _whitelistRegistry;
        // Access Control Setup
        // Grant pauser, beacon creator, and ERC165 editor roles to deployer for deploying initial beacons
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(BEACON_CREATOR, _msgSender());
        _setupRole(INTERFACE_EDITOR, _msgSender());
        // Grant admin role to deployer but after registering all initial beacons in its script, deployer will revoke all roles granted to self and grant default admin role and above three roles to multisig
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
    }

    /// @dev Creates a new vault with the given strategy
    /// @dev Registers an execution bundle, mints an NFT and mappings it to execution bundle and it's details.
    /// @param _params is extra parameters in vault.
    /// @param _tokenId is the NFT of the execution bundle this vault will be using. Note that if the given tokenID does not yet exist, the vault will remain inactive.
    /// @param _beaconName beacon identifier of vault type to be created
    /// @param _vaultManager is the address which will manage the vault being created
    /// @dev owner is set as msg.sender.
    function createVault(
        bytes memory _params,
        uint256 _tokenId,
        string memory _beaconName,
        address _vaultManager,
        string memory _payloadIpfs
    ) external whenNotPaused returns (address) {
        require(feeManager != address(0), "FeeManager not set");
        require(vaultHelper != address(0), "Vault helper not set");
        //Validate that no strategy exists of the tokenid passed
        strategyRegistry.ownerOf(_tokenId);
        // Retrieve the address for the vault type to be created
        address beaconAddress = beaconAddresses[_beaconName];
        // Make sure that we have a beacon for the provided vault type
        // This ensures that a bad vault type hasn't been provided
        require(beaconAddress != address(0), "Beacon is not present");
        // Create new vault implementation
        BeaconProxy newVault = new BeaconProxy(
            beaconAddress,
            abi.encodeWithSelector(
                IImplementation.initialize.selector,
                _vaultManager,
                orchestrator,
                owner(),
                _params
            )
        );

        // Add beacon type to mapping
        beaconTypes[address(newVault)] = _beaconName;
        // As the vaultypes will be gradually upgarded one by one there may be situations where
        // there may be a need to create older type of vaults(as they are not yet upgraded) which
        // do not depending on feemanager and also
        // there may be a need to craete the newer vaults which are depending on the fee manager so
        // to maintain backward compatibilty below piece of code is put in try catch block
        // Also in future there may be a vault type that does not use fee manager
        try IMultiPositionManager(address(newVault)).feeDetails() returns (
            uint256 totalFees,
            address[] memory feeWithdrawers,
            string[] memory feeIdentifiers,
            uint256[] memory feeValues
        ) {
            if (totalFees > 0) {
                IFeeManager(feeManager).setDefaultFeeAndWithdrawalPermission(
                    address(newVault),
                    totalFees,
                    feeIdentifiers,
                    feeValues,
                    feeWithdrawers
                );
            }
        } catch {
            emit FeeSettingFailed(
                address(newVault),
                "Fee setting failed in FeeManager"
            );
        }

        // Add enumeration for the vault
        _addLinkedVaultsEnumeration(
            _tokenId,
            address(newVault),
            _payloadIpfs,
            _beaconName
        );

        // Emit vault details
        emit VaultCreated(
            msg.sender,
            address(newVault),
            _beaconName,
            _tokenId,
            _vaultManager
        );

        // Return the address of the new vault
        return address(newVault);
    }

    /// @dev Updates the vault state and emits a VaultStateChanged event
    /// @param _vault The address of the vault
    /// @param _newState The new state of the vault
    /// @dev This function is only available to the registry owner.
    function updateVaultState(
        address _vault,
        VaultState _newState
    ) external onlyOwner {
        vaults[_vault].state = _newState;
        emit VaultStateChanged(_vault, _newState);
    }

    /// @dev Retrieves the creator of the strategy of the given vault
    /// @param _vault The address of the vault
    /// @return The address of the creator
    function getStrategyCreatorForVault(
        address _vault
    ) public view returns (address) {
        return strategyRegistry.ownerOf(vaults[_vault].tokenId);
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}

    /// @dev Pauses the minting of the ERC721 tokens for the vault
    /// @dev This function is only available to the pauser role
    function pause() public onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() public onlyRole(PAUSER_ROLE) {
        _unpause();
    }

    /// @dev Provides support for vault enumeration
    /// @dev Private function to add a token to this extension's ownership-tracking data structures.
    /// @param _tokenId uint256 ID of the token to be added to the tokens list of the given address
    /// @param _deployedAddress address of the new vault
    function _addLinkedVaultsEnumeration(
        uint256 _tokenId,
        address _deployedAddress,
        string memory _payloadIpfs,
        string memory _beaconName
    ) internal {
        // Get the current count of how many vaults have been created from this strategy.
        uint256 currentCount = linkedVaultCounts[_tokenId];

        // Using _tokenId and count as map keys, add the vault to the list of linked vaults
        linkedVaults[_tokenId][currentCount] = _deployedAddress;

        // Increment the count of how many vaults have been created from a given strategy
        linkedVaultCounts[_tokenId] = currentCount + 1;

        // Store any vault specific data via the _deployedAddress
        vaults[_deployedAddress] = VaultData({
            state: VaultState.PendingThreshold,
            tokenId: _tokenId,
            vaultID: ++totalVaultCount,
            payloadIpfs: _payloadIpfs,
            vaultAddress: _deployedAddress,
            beaconName: _beaconName
        });
    }

    /// @dev Retrieves the details of a given vault by address
    /// @param _address The address of the vault
    /// @return The details of the vault
    function getVaultDetails(
        address _address
    ) public view returns (VaultData memory) {
        return vaults[_address];
    }

    /// @dev Retrieves the vault count by vault token id
    /// @param _tokenId The token id of the vault
    /// @return The count of the vault
    function getVaultCountByStrategyId(
        uint256 _tokenId
    ) public view returns (uint256) {
        return linkedVaultCounts[_tokenId];
    }

    /// @dev Retrieves the vault by vault token id and vault index
    /// @param _tokenId The token id of the vault
    /// @param _vaultId The index of the vault
    /// @return Vault details
    function getVaultByStrategyAndIndex(
        uint256 _tokenId,
        uint256 _vaultId
    ) public view returns (VaultData memory) {
        return vaults[linkedVaults[_tokenId][_vaultId]];
    }

    function setFeeManager(address _feeManager) external onlyOwner {
        require(feeManager == address(0), "Already Set");
        feeManager = _feeManager;
    }

    function setVaultHelper(address _vaultHelper) external onlyOwner {
        vaultHelper = _vaultHelper;
    }
}

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

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @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 == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // ? `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // ? `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

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

import "./KeeperRegistry.sol";

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

// Proxy Support
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract RunnerRegistry is Initializable, UUPSUpgradeable, OwnableUpgradeable {
    // Runner info
    mapping(address => uint256) public runners;

    // Bonds
    uint256 public bondAmount; // Amount of bondcoin needed to qualify as a keeper
    IERC20 public bondToken; // Address of ERC20 used as bond
    uint256 public freeCoin; // Amount of bondcoin no longer affiliated with any runner (due to slashing etc.)

    // Slashing
    KeeperRegistry public keeperRegistry;

    event RunnerAmountChanged(address indexed runner, int256 amount);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    function initialize(
        address _keeperRegistry,
        uint256 _bondAmount,
        address _bondToken
    ) public initializer {
        __UUPSUpgradeable_init();
        __Ownable_init();
        keeperRegistry = KeeperRegistry(_keeperRegistry);
        bondAmount = _bondAmount;
        bondToken = IERC20(_bondToken);
    }

    /// @dev Check whether an address is runner or not.
    /// @param _runner is the address that needs to be checked.
    /// @return True if address is a runner ,if not returns false.
    function isRunner(address _runner) external view returns (bool) {
        return runners[_runner] >= bondAmount;
    }

    /// @dev Used to bond as a runner
    /// @param _amount is the amount of tokens that will be locked as bond amount.
    function bond(uint256 _amount) external {
        // Make sure the minimum bond is always met
        // if it is met, then allow any amount to be sent
        uint256 currentBonded = runners[msg.sender];
        require(
            currentBonded + _amount >= bondAmount,
            "Bonded amount must be above the minimum"
        );

        // Record the total amount the runner has deposited
        runners[msg.sender] = currentBonded + _amount;

        emit RunnerAmountChanged(msg.sender, int256(_amount));
        // Transfer the bond amount to the registry
        bondToken.transferFrom(msg.sender, address(this), _amount);
    }

    /// @dev Used to unbond as a runner and get the bonded tokens back.
    /// @param _amount is the amount of tokens that will be unlocked from bond amount.
    function unbond(uint256 _amount) external {
        runners[msg.sender] -= _amount;

        emit RunnerAmountChanged(msg.sender, int256(_amount) * -1);
        bondToken.transfer(msg.sender, _amount);
    }

    /// @dev Used to slash runners if they do a bad/malicious job.
    /// Only keepers can slash runners.
    /// @param _runner is the address of the runner who should be slashed.
    /// @param _amount is the amount which should be slashed from the runner's bond amount.
    function slash(address _runner, uint256 _amount) external {
        keeperRegistry.checkLicense(msg.sender);
        runners[_runner] -= _amount;
        freeCoin += _amount;
        emit RunnerAmountChanged(msg.sender, int256(_amount) * -1);
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}

    /// @dev Used to withdraw the tokens that were slashed from runners doing a bad/malicious job.
    /// Can only be called by the owner of this contract.
    /// @param _amount is the amount of tokens that needs to be withdrawn.
    function withdrawFreeCoin(uint256 _amount) external onlyOwner {
        freeCoin -= _amount;
        bondToken.transfer(msg.sender, _amount);
    }
}

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

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

enum YieldMode {
    AUTOMATIC,
    VOID,
    CLAIMABLE
}

interface IERC20Rebasing {
    // changes the yield mode of the caller and update the balance
    // to reflect the configuration
    function configure(YieldMode) external returns (uint256);
    // "claimable" yield mode accounts can call this this claim their yield
    // to another address
    function claim(address recipient, uint256 amount) external returns (uint256);
    // read the claimable amount for an account
    function getClaimableAmount(address account) external view returns (uint256);
}

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

// Proxy Support
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { IBundleRegistry } from "./interfaces/IBundleRegistry.sol";

contract BundleRegistry is
    IBundleRegistry,
    Initializable,
    OwnableUpgradeable,
    UUPSUpgradeable,
    PausableUpgradeable
{
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    // Storage

    mapping(bytes32 => DataSourceAdapter) public bundles;

    function initialize() public initializer {
        __UUPSUpgradeable_init();
        __Ownable_init();
        __Pausable_init();
    }

    /// @dev Registers an execution bundle, making data accessible to nodes.
    ///      Once registered, each bundle is immutable.
    ///      Only the contract owner can change it, and they can only
    ///      deactivate/reactivate it if necessary.
    /// @param _bundle The bundle of the transformation module.
    /// @param _source The source of the transformation module source (e.g. "The Graph" or "Twitter")
    /// @param _host The host of the transformation module source (e.g. "Uniswap" or "@random-twitter-username")
    /// @param _output The output type of the adapter, example: OHLC, OHLCV, SingleValue, etc.
    /// @param _active Determines if the adapter is active or not, allows for inactive sources
    ///                to be deprecated, vaults can pause based on this
    function register(
        string memory _bundle,
        string memory _source,
        string memory _host,
        string memory _output,
        string memory _infoHash,
        bool _active
    ) external whenNotPaused {
        require(isIPFS(_bundle), "Bundle must be an IPFS CID hash");

        bytes32 bundleHash = keccak256(
            abi.encode(_bundle, _source, _host, _output)
        );

        // Check that bundle does not yet have an author--proxy
        // to check whether the bundle was already registered.

        require(
            bundles[bundleHash].author == address(0),
            "Bundle already registered"
        );

        // Record bundle
        bundles[bundleHash] = DataSourceAdapter({
            bundle: _bundle,
            source: _source,
            host: _host,
            output: _output,
            info: _infoHash,
            active: _active,
            author: _msgSender()
        });

        // Emit the event that the bundle was created
        emit BundleRegistered(
            bundleHash,
            _bundle,
            _host,
            _source,
            _output,
            _infoHash,
            _active,
            msg.sender
        );
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}

    function pause() external onlyOwner {
        _pause();
    }

    /// @dev onlyOwner function to deprecate (or reactivate) an existing adapter.
    /// @param _adapter The key of the adapter to pause.
    /// @param _remainActive Whether to pause or unpause; false to pause.
    function setAdapterState(
        bytes32 _adapter,
        bool _remainActive
    ) external onlyOwner {
        bundles[_adapter].active = _remainActive;
        emit BundleStateChange(_adapter, _remainActive);
    }

    /// @dev Checks if the passed string is a IPFS link or not.
    /// @param source String that needs to checked.
    /// @return true if the string passed is IPFS, else it will return false.
    function isIPFS(string memory source) internal pure returns (bool) {
        bytes memory sourceToBytes = bytes(source);
        require(sourceToBytes.length == 46, "Length");
        bytes memory firstChar = new bytes(1);
        bytes memory secondChar = new bytes(1);
        bytes memory lastChar = new bytes(1);
        firstChar[0] = sourceToBytes[0];
        secondChar[0] = sourceToBytes[1];
        lastChar[0] = sourceToBytes[45];
        return
            keccak256(firstChar) == keccak256(bytes("Q")) &&
            keccak256(secondChar) == keccak256(bytes("m")) &&
            (keccak256(lastChar) != keccak256(bytes("O")) &&
                keccak256(lastChar) != keccak256(bytes("I")) &&
                keccak256(lastChar) != keccak256(bytes("l")));
    }
}

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

import { BeaconManager } from "./BeaconManager.sol";

abstract contract InterfaceManager is BeaconManager {
    bytes32 internal constant INTERFACE_EDITOR = keccak256("INTERFACE_EDITOR");

    error IncorrectArrayLengths(
        uint256 selectorLength,
        uint256 isImplementedLength
    );

    /**
     * @dev mapping beacon name => function selector => isImplemented
     */
    mapping(string => mapping(bytes4 => bool)) public interfaceImplementations;

    /**
     * @dev add interface info to given beacon
     */
    function updateInterfaceImplementations(
        string calldata beaconName,
        bytes4[] calldata selectors,
        bool[] calldata isImplemented
    ) external onlyRole(INTERFACE_EDITOR) {
        // Require that array lengths match
        if (selectors.length != isImplemented.length) {
            revert IncorrectArrayLengths(
                selectors.length,
                isImplemented.length
            );
        }

        // Set
        for (uint256 i; i != selectors.length; ++i) {
            interfaceImplementations[beaconName][selectors[i]] = isImplemented[
                i
            ];
        }
    }

    /**
     * @dev check whether msg.sender supports a given interface id. Used to support ERC165 from a central location.
     * @param interfaceId the interface id to check
     */
    function doISupportInterface(bytes4 interfaceId)
        external
        view
        returns (bool)
    {
        string memory beaconOfSender = beaconTypes[msg.sender];
        return interfaceImplementations[beaconOfSender][interfaceId];
    }
}

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

import "./StellaSwapV4BaseLiquidityManager.sol";

contract StellaSwapV4MultiPositionLiquidityManager is
    StellaSwapV4BaseLiquidityManager
{
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

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

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 CountersUpgradeable {
    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;
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Permissioned pool actions
/// @notice Contains pool methods that may only be called by the factory owner
interface IUniswapV3PoolOwnerActions {
    /// @notice Set the denominator of the protocol's % share of the fees
    /// @param feeProtocol0 new protocol fee for token0 of the pool
    /// @param feeProtocol1 new protocol fee for token1 of the pool
    function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;

    /// @notice Collect the protocol fee accrued to the pool
    /// @param recipient The address to which collected protocol fees should be sent
    /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
    /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
    /// @return amount0 The protocol fee collected in token0
    /// @return amount1 The protocol fee collected in token1
    function collectProtocol(
        address recipient,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external returns (uint128 amount0, uint128 amount1);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
    function __AccessControl_init() internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __AccessControl_init_unchained();
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role, _msgSender());
        _;
    }

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

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        StringsUpgradeable.toHexString(uint160(account), 20),
                        " is missing role ",
                        StringsUpgradeable.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
    uint256[49] private __gap;
}

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

pragma solidity ^0.8.0;

import "../ERC721Upgradeable.sol";
import "../../../security/PausableUpgradeable.sol";
import "../../../proxy/utils/Initializable.sol";

/**
 * @dev ERC721 token with pausable token transfers, minting and burning.
 *
 * Useful for scenarios such as preventing trades until the end of an evaluation
 * period, or having an emergency switch for freezing all token transfers in the
 * event of a large bug.
 */
abstract contract ERC721PausableUpgradeable is Initializable, ERC721Upgradeable, PausableUpgradeable {
    function __ERC721Pausable_init() internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __Pausable_init_unchained();
        __ERC721Pausable_init_unchained();
    }

    function __ERC721Pausable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {ERC721-_beforeTokenTransfer}.
     *
     * Requirements:
     *
     * - the contract must not be paused.
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        require(!paused(), "ERC721Pausable: token transfer while paused");
    }
    uint256[50] private __gap;
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";
import { IBlastPoints } from "contracts/interfaces/IBlastPoints.sol";
import { IBlast } from "contracts/interfaces/IBlast.sol";
import "contracts/interfaces/IERC20Rebasing.sol";

abstract contract FenixBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0x7a44CD060afC1B6F4c80A2B9b37f4473E74E25Df);

    IBlast private constant BLAST =
        IBlast(0x4300000000000000000000000000000000000002);
    address private constant BLAST_POINTS =
        0x2536FE9ab3F511540F2f9e2eC2A805005C3Dd800;
    IERC20Rebasing private constant USDB =
        IERC20Rebasing(0x4300000000000000000000000000000000000003);
    IERC20Rebasing private constant WETHB =
        IERC20Rebasing(0x4300000000000000000000000000000000000004);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        IBlastPoints(BLAST_POINTS).configurePointsOperator(
            0xeD117947DF69DA172E4794b8E5E9828E37544F7E
        );
        USDB.configure(YieldMode.CLAIMABLE);
        WETHB.configure(YieldMode.CLAIMABLE);
        BLAST.configureGovernor(_steer);
        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract IntegralBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0xC848bc597903B4200b9427a3d7F61e3FF0553913);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

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

pragma solidity ^0.8.0;

import "../StringsUpgradeable.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSAUpgradeable {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        } else if (error == RecoverError.InvalidSignatureV) {
            revert("ECDSA: invalid signature 'v' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        // Check the signature length
        // - case 65: r,s,v signature (standard)
        // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else if (signature.length == 64) {
            bytes32 r;
            bytes32 vs;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly {
                r := mload(add(signature, 0x20))
                vs := mload(add(signature, 0x40))
            }
            return tryRecover(hash, r, vs);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s;
        uint8 v;
        assembly {
            s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
            v := add(shr(255, vs), 27)
        }
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ? {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }
        if (v != 27 && v != 28) {
            return (address(0), RecoverError.InvalidSignatureV);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", StringsUpgradeable.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.12;
import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol";

interface IStrategyRegistry is
    IERC721Upgradeable,
    IERC721EnumerableUpgradeable
{
    struct RegisteredStrategy {
        uint256 id;
        string name;
        address owner;
        string execBundle; //IPFS reference of execution bundle
        //GasVault stuff
        uint128 maxGasCost;
        uint128 maxGasPerAction;
    }

    function getStrategyAddress(uint256 tokenId)
        external
        view
        returns (address);

    function getStrategyOwner(uint256 tokenId) external view returns (address);

    /**
     * @dev Create NFT for execution bundle.
     * @param name The name of the strategy.
     * @param execBundle The IPFS reference of the execution bundle.
     * @return newStrategyTokenId The token ID of the NFT.
     */
    function createStrategy(
        address strategyCreator,
        string memory name,
        string memory execBundle,
        uint128 maxGasCost,
        uint128 maxGasPerAction
    ) external returns (uint256 newStrategyTokenId);

    //
    // Todo: add to utility library
    //
    function addressToString(address _address)
        external
        pure
        returns (string memory);

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC721Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() external;

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC721Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() external;

    function tokenURI(uint256 tokenId) external view returns (string memory);

    function getRegisteredStrategy(uint256 tokenId)
        external
        view
        returns (IStrategyRegistry.RegisteredStrategy memory);

    /**
     * @dev parameters users set for what constitutes an acceptable use of their funds. Can only be set by NFT owner.
     * @param _tokenId is the token ID of the execution bundle.
     * @param _maxGasCost is highest acceptable price to pay per gas, in terms of gwei.
     * @param _maxGasPerMethod is max amount of gas to be sent in one method.
     * @param _maxMethods is the maximum number of methods that can be executed in one action.
     */
    function setGasParameters(
        uint256 _tokenId,
        uint128 _maxGasCost,
        uint128 _maxGasPerMethod,
        uint16 _maxMethods
    ) external;

    //function getExecutionBundle(uint256 tokenId) external view returns (string memory);

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

    function burn(uint256 tokenId) external;

    function supportsInterface(bytes4 interfaceId)
        external
        view
        returns (bool);
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract GlyphBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0x74EfE55beA4988e7D92D03EFd8ddB8BF8b7bD597);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

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

pragma solidity ^0.8.0;

import "./IERC165Upgradeable.sol";

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165CheckerUpgradeable {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface,
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            _supportsERC165Interface(account, type(IERC165Upgradeable).interfaceId) &&
            !_supportsERC165Interface(account, _INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return supportsERC165(account) && _supportsERC165Interface(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     *
     * _Available since v3.4._
     */
    function getSupportedInterfaces(address account, bytes4[] memory interfaceIds)
        internal
        view
        returns (bool[] memory)
    {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = _supportsERC165Interface(account, interfaceIds[i]);
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in _interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!_supportsERC165Interface(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     * Interface identification is specified in ERC-165.
     */
    function _supportsERC165Interface(address account, bytes4 interfaceId) private view returns (bool) {
        bytes memory encodedParams = abi.encodeWithSelector(IERC165Upgradeable.supportsInterface.selector, interfaceId);
        (bool success, bytes memory result) = account.staticcall{gas: 30000}(encodedParams);
        if (result.length < 32) return false;
        return success && abi.decode(result, (bool));
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

interface IRewardOrchestrator {
    enum State {
        PENDING,
        COMPLETED
    }

    /// @dev Triggered when a hash change request/proposal is executed.
    event ExecutedHashChangeRequest(uint256 indexed requestId, bytes32 hash);

    function actionApprovalStatus(uint256 _hashId)
        external
        view
        returns (bool);

    function createHashChangeRequest(bytes32 _hash) external returns (uint256);

    function voteOnHashChangeRequest(uint256 _hashId, bool vote) external;

    function executeHashChangeRequest(uint256 _hashId)
        external
        returns (State);
}

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

// ERC support
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Math
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; // max(), min(), and average
// Algebra
import { IAlgebraFactory } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraFactory.sol";
import { IAlgebraPool } from "@cryptoalgebra/integral-core/contracts/interfaces/IAlgebraPool.sol";
import { TickMath } from "@cryptoalgebra/integral-core/contracts/libraries/TickMath.sol";
import { FullMath } from "@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol";
import { IAlgebraMintCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraMintCallback.sol";
import { IAlgebraSwapCallback } from "@cryptoalgebra/integral-core/contracts/interfaces/callback/IAlgebraSwapCallback.sol";
import { LiquidityAmounts } from "@cryptoalgebra/integral-periphery/contracts/libraries/LiquidityAmounts.sol";
import { PositionKey } from "@cryptoalgebra/integral-periphery/contracts/libraries/PositionKey.sol";
import { IVolatilityOracle } from "@cryptoalgebra/integral-base-plugin/contracts/interfaces/plugins/IVolatilityOracle.sol";

// Util support
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";

// Sundry
import { IBareVaultRegistry8 } from "contracts/interfaces/IBareVaultRegistry8.sol";
import { IBaseDeposit } from "contracts/interfaces/IBaseDeposit.sol";
import { IFeeManager } from "../../interfaces/IFeeManager.sol";

abstract contract SilverSwapBaseLiquidityManager is
    Initializable,
    ERC20Upgradeable,
    PausableUpgradeable,
    AccessControlUpgradeable,
    IAlgebraMintCallback,
    IAlgebraSwapCallback
{
    using SafeERC20 for IERC20;
    // Storage

    uint256 internal constant PRECISION = 1e18;

    /// @dev Can collect Steer fees
    bytes32 internal constant STEER_ROLE = keccak256("STEER_ROLE");
    /// @dev Can call tend function
    bytes32 internal constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    /// @notice Fee info
    /// @dev Fee rates, each multiplied by 10,000
    ///      (a TOTAL_FEE of 100 means a 1% cut of total Algebra fees)
    /// @dev Total fraction of fees not going towards LPs, multiplied by 10,000
    uint256 public constant TOTAL_FEE = 15_00;
    /// @dev Total fraction of fees going towards Steer (as opposed to going towards strategist)
    uint256 public constant STEER_FRACTION_OF_FEE = 66_67;

    uint256 public constant STRATEGIST_FRACTION_OF_FEE = 33_33;

    uint256 internal constant FEE_DIVISOR = 100_00;
    uint256 internal constant DIVISOR = 100_00;
    /// @dev maximum value that can be passed in total weight parameter of tend
    uint256 internal constant TOTAL_WEIGHT_MAX = 100_00;

    uint256 internal constant DIVISOR100 = 1_00;

    uint256 internal constant FIVE = 5;

    /// @dev maxTickChange value should be greater than MAX_TICK_CHANGE_MIN
    int24 internal constant MAX_TICK_CHANGE_MIN = 9;
    /// @dev maxTickChange value should be less than MAX_TICK_CHANGE_MAX
    int24 internal constant MAX_TICK_CHANGE_MAX = 2_001;
    /// @dev twapInterval value should be greater than TWAP_INTERVAL_MIN
    uint32 internal constant TWAP_INTERVAL_MIN = 3_0;
    /// @dev twapInterval value should be less than TWAP_INTERVAL_MAX
    uint32 internal constant TWAP_INTERVAL_MAX = 6_00;
    /// @dev first deposit should mint MIN_SHARES or greater number of shares
    uint256 internal constant MIN_SHARES = 100_00_00;

    /// @dev Address of Algebra Factory
    IAlgebraFactory internal constant FACTORY =
        IAlgebraFactory(0xb860200BD68dc39cEAfd6ebb82883f189f4CdA76);

    /// @dev Address of vault registry
    ///      Address strategist can collect strategist fees, but is not stored here.
    address internal vaultRegistry;

    /// @notice Addresses of Token0 and Token1
    IERC20 public token0;
    IERC20 public token1;

    /// @notice Address of Algebra pool
    IAlgebraPool public pool;

    /// @dev For depositing
    /// Roughly corresponds to a 5% diff between current price and twap price
    int24 public maxTickChange;

    /// @dev Number of seconds to get the time-weighted average over
    uint32 public twapInterval;

    //For mint Callback Protection
    bool internal mintCallBackProtection;

    //For swap Callback Protection
    bool internal swapCallBackProtection;
    mapping(string => uint256) public accruedFees0;
    mapping(string => uint256) public accruedFees1;

    address internal feeManager;
    uint256 public totalFees0;
    uint256 public totalFees1;

    // Events

    /// @dev Pool/vault info as of the end of a tend
    /// @param sqrtPriceX96 Current pool price
    /// @param totalAmount0 The total user-owned token0, including funds held in the vault
    ///                     and funds deposited into the pool
    /// @param totalSupply The total number of shares in this vault.
    event Snapshot(
        uint160 sqrtPriceX96,
        uint256 totalAmount0,
        uint256 totalAmount1,
        uint256 totalSupply
    );

    /// @dev Deposit info
    /// @param sender The address which provided the tokens
    /// @param to The receiver of vault shares from this deposit, generally the same as sender
    /// @param shares The number of shares that have been minted by this deposit
    /// @param amount0 The amount of t0 that was used to mint the shares
    /// @param amount1 The amount of t1 that was used to mint the shares
    event Deposit(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Withdraw info
    /// @param sender msg.sender and the owner of the shares being burned
    /// @param to The receiver of the tokens earned via the burn
    /// @param shares The number of shares being burned
    /// @param amount0 The amount of t0 earned via the burn
    /// @param amount1 The amount of t1 earned via the burn
    event Withdraw(
        address indexed sender,
        address indexed to,
        uint256 shares,
        uint256 amount0,
        uint256 amount1
    );

    /// @dev Info on protocol fees earned. Triggers whenever a withdraw or a tend happens.
    /// @param amount0Earned Total t0 fees earned, including fees going to steer and strategist.
    /// @param amount1Earned Total t1 fees earned, including fees going to steer and strategist.
    event FeesEarned(uint256 amount0Earned, uint256 amount1Earned);

    // Constructor

    constructor() initializer {}

    // External Functions

    /// @notice Withdraws tokens in proportion to the vault's holdings.
    /// @param shares Shares burned by sender
    /// @param amount0Min Revert if resulting `amount0` is smaller than this
    /// @param amount1Min Revert if resulting `amount1` is smaller than this
    /// @param to Recipient of tokens
    /// @return amount0 Amount of token0 sent to recipient
    /// @return amount1 Amount of token1 sent to recipient
    function withdraw(
        uint256 shares,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    ) external virtual returns (uint256 amount0, uint256 amount1) {
        require(to != address(0));
        // Shares to withdraw must be greater than zero
        require(shares > 0);

        uint256 _totalSupply = totalSupply();

        // Burn LPTs
        _burn(msg.sender, shares);

        // Calculate token amounts proportional to unused balances
        // LP value = (Value deposited in Algebra + value held in vault undeposited) / total LP tokens.
        // Here we calculate value held in vault undeposited.
        // No div(0) safemath here because totalSupply != 0;
        // since shares != 0 and totalSupply >= shares
        amount0 = FullMath.mulDiv(_getBalance0(), shares, _totalSupply);
        amount1 = FullMath.mulDiv(_getBalance1(), shares, _totalSupply);

        // Withdraw proportion of liquidity from Algebra pool
        (uint256 t0FromPool, uint256 t1FromPool) = _burnAndCollect(
            shares,
            _totalSupply
        );

        // In addition to their share of vault-held tokens,
        // withdraw their share of Algebra-held tokens.
        amount0 += t0FromPool;
        amount1 += t1FromPool;

        require(amount0 >= amount0Min);
        require(amount1 >= amount1Min);

        // Push tokens to recipient
        _transferTokens(to, amount0, amount1);

        emit Withdraw(msg.sender, to, shares, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraMintCallback(
        uint256 amount0,
        uint256 amount1,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(mintCallBackProtection);
        mintCallBackProtection = false;
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @dev Callback for Algebra pool.
    function algebraSwapCallback(
        int256 amount0Wanted,
        int256 amount1Wanted,
        bytes calldata /* data */
    ) external override {
        require(msg.sender == address(pool));
        require(swapCallBackProtection);
        swapCallBackProtection = false;
        uint256 amount0;
        uint256 amount1;
        if (amount0Wanted > 0) {
            amount0 = uint256(amount0Wanted);
        }
        if (amount1Wanted > 0) {
            amount1 = uint256(amount1Wanted);
        }
        _transferTokens(msg.sender, amount0, amount1);
    }

    /// @notice Removes liquidity in case of emergency.
    function emergencyBurn(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    )
        external
        onlyRole(STEER_ROLE)
        returns (uint256 amount0, uint256 amount1)
    {
        (amount0, amount1) = pool.burn(tickLower, tickUpper, liquidity, "");
        pool.collect(
            address(this),
            tickLower,
            tickUpper,
            type(uint128).max,
            type(uint128).max
        );
    }

    /// @notice Used by integral-periphery to verify vault is depositable.
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            IBareVaultRegistry8(vaultRegistry).doISupportInterface(
                interfaceId
            );
    }

    /// @dev Pause and unpause
    function pause() external onlyRole(STEER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(STEER_ROLE) {
        _unpause();
    }

    // Public Functions

    /// @dev Initializes vault
    /// @param _vaultManager is the address which will manage the vault being created, pass orchestrator address if the vault is meant to be managed by the orchestrator
    /// @param _steer The steer multisig address, responsible for some governance functions.
    /// @param _params All other parameters this vault will use
    function initialize(
        address _vaultManager,
        address, //orchestrator not needed here as, if this vault is to be managed by orchestrator, _vaultManager parameter should be the orchestrator address
        address _steer,
        bytes memory _params
    ) public virtual initializer {
        // _token0 is address of token0
        // _token1 is address of token1
        // _maxTickChange is max difference between TWAP tick and current tick,
        //      useful for protection against frontrunning. Cannot be negative.
        // _twapInterval is the number of seconds to get the time-weighted average over
        (
            address _token0,
            address _token1,
            int24 _maxTickChange,
            uint32 _twapInterval
        ) = abi.decode(_params, (address, address, int24, uint32));

        // Validate input parameters
        // Below values should be chosen according to the chain this contract is deployed on and
        // the volume of the pool it manages
        require(
            _maxTickChange > MAX_TICK_CHANGE_MIN &&
                _maxTickChange < MAX_TICK_CHANGE_MAX
        );
        require(
            _twapInterval > TWAP_INTERVAL_MIN &&
                _twapInterval < TWAP_INTERVAL_MAX
        );
        require(_token0 < _token1);

        // Context inits
        vaultRegistry = msg.sender;
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);

        // Get relevant pool from Algebra factory
        address _pool = FACTORY.poolByPair(_token0, _token1);

        // Revert if pool doesn't exist yet
        require(_pool != address(0));

        // Set pool
        pool = IAlgebraPool(_pool);
        address _plugin = IAlgebraPool(_pool).plugin();
        require(_plugin != address(0));
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;
        IVolatilityOracle(_plugin).getTimepoints(secondsAgos); //Just to check if this pool has a oracle plugin if not reverts from here
        feeManager = IBareVaultRegistry8(vaultRegistry).feeManager();

        // Init the LP token ERC20 using totalVaultCount from the vaultRegistry
        // For e.g. if current vault count is 20 than LP name is STEER_ALGEBRA_VAULT_20
        string memory vaultCount = StringsUpgradeable.toString(
            IBareVaultRegistry8(msg.sender).totalVaultCount() + 1
        );
        __ERC20_init(
            string(abi.encodePacked("STEER_ALGEBRA_VAULT_", vaultCount)),
            string(abi.encodePacked("STEERAV", vaultCount))
        );

        // Init everything else
        __Pausable_init();
        __AccessControl_init();

        _setupRole(STEER_ROLE, _steer);
        _setupRole(MANAGER_ROLE, _vaultManager);

        // Set security params
        maxTickChange = _maxTickChange;
        twapInterval = _twapInterval;
    }

    /// @notice Deposits tokens in proportion to the vault's current holdings.
    /// @dev These tokens sit in the vault and are not used for liquidity on
    ///      Algebra until the next rebalance.
    ///      function requirements:
    ///        Either amount0Desired or amount1Desired must be > 0 or it will revert with 'CROSS'
    /// @param amount0Desired Max amount of token0 to deposit
    /// @param amount1Desired Max amount of token1 to deposit
    /// @param amount0Min Revert if resulting amount0Used is less than this
    /// @param amount1Min Revert if resulting amount1Used is less than this
    /// @param to Recipient of shares
    /// @return shares Number of shares minted
    /// @return amount0Used Amount of token0 deposited
    /// @return amount1Used Amount of token1 deposited
    function deposit(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 amount0Min,
        uint256 amount1Min,
        address to
    )
        public
        virtual
        whenNotPaused
        returns (uint256 shares, uint256 amount0Used, uint256 amount1Used)
    {
        require(to != address(0));
        IBareVaultRegistry8.VaultState state = IBareVaultRegistry8(
            vaultRegistry
        ).getVaultDetails(address(this)).state;
        require(
            state == IBareVaultRegistry8.VaultState.PendingThreshold ||
                state == IBareVaultRegistry8.VaultState.Active
        );
        // Update pool fees earned
        poke();

        // Calculate amounts proportional to vault's holdings
        uint256 _totalSupply = totalSupply();
        (uint256 total0, uint256 total1) = getTotalAmounts();

        // If total supply > 0, vault can't be empty.
        assert(_totalSupply == 0 || total0 > 0 || total1 > 0);

        if (_totalSupply == 0) {
            // For first deposit, just use the amounts desired
            amount0Used = amount0Desired;
            amount1Used = amount1Desired;
            shares = Math.max(amount0Used, amount1Used);
            require(shares >= MIN_SHARES);
        } else if (total0 == 0) {
            shares = FullMath.mulDiv(amount1Desired, _totalSupply, total1);
            amount1Used = FullMath.mulDivRoundingUp(
                shares,
                total1,
                _totalSupply
            );
        } else if (total1 == 0) {
            shares = FullMath.mulDiv(amount0Desired, _totalSupply, total0);
            amount0Used = FullMath.mulDivRoundingUp(
                shares,
                total0,
                _totalSupply
            );
        } else {
            uint256 cross = Math.min(
                amount0Desired * total1,
                amount1Desired * total0
            );

            // If cross is zero, this means that the inputted ratio is totally wrong
            // and must be adjusted to better match the vault's held ratio.
            // This pretty much only happens if all of the vault's holdings are in one token,
            // and the user wants to exclusively deposit the other token.
            require(cross > 0, "C");

            // Round up amounts
            // cross - 1 can be unchecked since above we require cross != 0
            // total1 and total0 are also both > 0
            amount0Used = ((cross - 1) / total1) + 1;
            amount1Used = ((cross - 1) / total0) + 1;

            shares = FullMath.mulDiv(cross, _totalSupply, total0) / total1;
        }

        // Make sure deposit meets slippage requirements.
        // If amount0Used < amount0Min or amount1Used < amount1Min,
        // there has been too much slippage.
        require(shares > 0);
        require(amount0Used >= amount0Min, "0");
        require(amount1Used >= amount1Min, "1");

        // Pull in tokens from sender
        if (amount0Used > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0Used);
        }
        if (amount1Used > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1Used);
        }

        // Mint shares to recipient
        _mint(to, shares);
        emit Deposit(msg.sender, to, shares, amount0Used, amount1Used);
    }

    function poke() public virtual;

    function getTotalAmounts() public view virtual returns (uint256, uint256);

    // Internal Functions

    /// @dev Wrapper around `IAlgebraPool.positions()`.
    /// @param tickLower Lower bound of position whose info is requested
    /// @param tickUpper Upper bound of position
    /// @return liquidity The amount of liquidity owned by this position
    /// @return feeGrowthInside0LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return feeGrowthInside1LastX128 Fee growth per unit of liquidity
    ///           as of the last update to liquidity or fees owed
    /// @return tokensOwed0 The fees owed to the position owner in token0
    /// @return tokensOwed1 The fees owed to the position owner in token1
    function _position(
        int24 tickLower,
        int24 tickUpper
    ) internal view returns (uint256, uint256, uint256, uint128, uint128) {
        return
            pool.positions(
                PositionKey.compute(address(this), tickLower, tickUpper)
            );
    }

    function _transferTokens(
        address to,
        uint256 amount0,
        uint256 amount1
    ) internal {
        if (amount0 > 0) {
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            token1.safeTransfer(to, amount1);
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares Total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal virtual returns (uint256 t0, uint256 t1);

    /// @dev revert if volatility is above acceptable levels
    ///      (mainly used to prevent flashloan attacks)
    /// @param currentTick Current pool tick
    function _checkVolatility(int24 currentTick) internal {
        // SLOADS for efficiency
        uint32 _twapInterval = twapInterval;

        int24 _maxTickChange = maxTickChange;

        // Get TWAP tick
        uint32[] memory secondsAgos = new uint32[](2);
        secondsAgos[0] = _twapInterval;

        // tickCumulatives is basically where the tick was as of twapInterval seconds ago
        try
            IVolatilityOracle(IAlgebraPool(pool).plugin()).getTimepoints(
                secondsAgos
            )
        returns (int56[] memory tickCumulatives, uint88[] memory) {
            int56 tickCumulativesDelta = tickCumulatives[1] -
                tickCumulatives[0];

            int24 twapTick = int24(
                tickCumulativesDelta / int32(_twapInterval)
            );

            // Always round to negative infinity
            if (
                tickCumulativesDelta < 0 &&
                (tickCumulativesDelta % int56(uint56(_twapInterval)) != 0)
            ) {
                twapTick--;
            }

            // Make sure currentTick is not more than maxTickChange ticks away from twapTick
            // No SafeMath here--even if a compromised governance contract set _maxTickChange to a very high value,
            // it would only wrap around and cause this check to fail.
            require(
                currentTick <= twapTick + _maxTickChange &&
                    currentTick >= twapTick - _maxTickChange,
                "V"
            );
        } catch {
            _pause();
            // Pause the vault so pause new deposits,collectFees and tend until there is a oracle plugin/function
            // To collect fees the vault should be unpaused and it should not be a problem even by chance a tend is called
            // it should again reach here and pause the vault again also it should be unpaused after a proper oracle
            // is setup to resume working
            return;
        }
    }

    /// @notice Balance of token0 in vault not used in any position.
    function _getBalance0() internal view returns (uint256) {
        return (token0.balanceOf(address(this)) - totalFees0);
    }

    /// @notice Balance of token1 in vault not used in any position.
    function _getBalance1() internal view returns (uint256) {
        return (token1.balanceOf(address(this)) - totalFees1);
    }

    function collectFees(
        string memory feeIdentifier,
        uint256 amount0,
        uint256 amount1
    ) external whenNotPaused {
        require(msg.sender == feeManager);
        address to = IFeeManager(feeManager).withdrawalPermissions(
            address(this),
            feeIdentifier
        );
        uint256 currentFee0 = accruedFees0[feeIdentifier];
        uint256 currentFee1 = accruedFees1[feeIdentifier];

        if (amount0 > 0) {
            accruedFees0[feeIdentifier] = currentFee0 - amount0;
            totalFees0 -= amount0;
            token0.safeTransfer(to, amount0);
        }
        if (amount1 > 0) {
            accruedFees1[feeIdentifier] = currentFee1 - amount1;
            totalFees1 -= amount1;
            token1.safeTransfer(to, amount1);
        }
    }

    function feeDetails()
        external
        view
        returns (uint256, address[] memory, string[] memory, uint256[] memory)
    {
        address[] memory feeWithdrawerAddresses = new address[](2);
        feeWithdrawerAddresses[0] = IBareVaultRegistry8(vaultRegistry).owner(); //steer withdrawer
        feeWithdrawerAddresses[1] = IBareVaultRegistry8(vaultRegistry)
            .getStrategyCreatorForVault(address(this)); //strategist withdrawer
        string[] memory feeIdentifiers = new string[](2);
        feeIdentifiers[0] = "STEER_FEES";
        feeIdentifiers[1] = "STRATEGIST_FEES";
        uint256[] memory feeValues = new uint256[](2);
        feeValues[0] = STEER_FRACTION_OF_FEE;
        feeValues[1] = STRATEGIST_FRACTION_OF_FEE;
        return (TOTAL_FEE, feeWithdrawerAddresses, feeIdentifiers, feeValues);
    }

    // Gap

    /// @dev Useful if we upgrade to a contract which needs more storage slots
    /// This contract consumes 9 slots and following openzeppelin standards(50 - slots consumed by this contract i.e.50 - 9 = 41) the gap array size is set to 41
    uint256[41] private gap;
}

File 168 of 177 : IERC1967.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)

pragma solidity ^0.8.0;

/**
 * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
 *
 * _Available since v4.8.3._
 */
interface IERC1967 {
    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Emitted when the beacon is changed.
     */
    event BeaconUpgraded(address indexed beacon);
}

File 169 of 177 : PositionKey.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

library PositionKey {
    /// @dev Returns the key of the position in the core library
    function compute(
        address owner,
        int24 bottomTick,
        int24 topTick
    ) internal pure returns (bytes32 key) {
        assembly {
            key := or(shl(24, or(shl(24, owner), and(bottomTick, 0xFFFFFF))), and(topTick, 0xFFFFFF))
        }
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.12;

import "./interfaces/IGasVault.sol";
import "./interfaces/IOrchestrator.sol";
import "./interfaces/IStrategyRegistry.sol";
import "./interfaces/IVaultRegistry.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

/**
 * @dev vault for storing gas for each strategy. Nodes must still pay gas cost to call, but execution costs
 *  will come out of the gas account.
 */
contract GasVault is
    IGasVault,
    Initializable,
    OwnableUpgradeable,
    UUPSUpgradeable,
    ReentrancyGuardUpgradeable
{
    // Storage

    IOrchestrator public orchestrator;
    IStrategyRegistry public strategyRegistry;
    IVaultRegistry public vaultRegistry;

    /// @notice Mapping from vault address to gasInfo
    mapping(address => uint256) public ethBalances;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    /// @dev Permanently sets related addresses
    /// @param _orchestrator Address of the orchestrator contract
    /// @param _stratRegistry Address of the strategy registry contract
    /// @param _vaultRegistry Address of the vault registry contract
    function initialize(
        address _orchestrator,
        address _stratRegistry,
        address _vaultRegistry
    ) public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
        __ReentrancyGuard_init();
        require(_orchestrator != address(0), "address(0)");
        require(_stratRegistry != address(0), "address(0)");
        require(_vaultRegistry != address(0), "address(0)");
        orchestrator = IOrchestrator(_orchestrator);
        strategyRegistry = IStrategyRegistry(_stratRegistry);
        vaultRegistry = IVaultRegistry(_vaultRegistry);
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}

    modifier onlyOrchestrator() {
        require(
            msg.sender == address(orchestrator),
            "Only orchestrator can call this"
        );
        _;
    }

    /// @dev Deposit more eth to be used in jobs.
    ///      Can only be withdrawn by governance and the given vault,
    ///      so in most cases these funds are unretrievable.
    /// @param targetAddress address of the recipient of these gas funds
    function deposit(address targetAddress) external payable override {
        ethBalances[targetAddress] += msg.value;
        emit Deposited(msg.sender, targetAddress, msg.value);
    }

    /// @dev Normal withdraw function, normally used by keepers
    /// @param amount The amount to withdraw
    /// @param to Address to send the ether to
    function withdraw(
        uint256 amount,
        address payable to
    ) external override nonReentrant {
        ethBalances[msg.sender] -= amount;
        emit Withdrawn(msg.sender, to, amount);
        AddressUpgradeable.sendValue(to, amount);
    }

    /// @param targetAddress The address of the vault in question
    /// @param highGasEstimate An estimate of the highest reasonable gas price which
    ///                        a transaction will cost, in terms of wei.
    ///                        In other words, given a bad gas price,
    ///                        how many more times can a strategy be run.
    /// @return transactions Remaining, assuming upper limit estimate of gas price
    ///                      is used for the transaction
    function transactionsRemaining(
        address targetAddress,
        uint256 highGasEstimate
    ) external view override returns (uint256) {
        IVaultRegistry.VaultData memory vaultInfo = vaultRegistry
            .getVaultDetails(targetAddress);
        IStrategyRegistry.RegisteredStrategy memory info = strategyRegistry
            .getRegisteredStrategy(vaultInfo.tokenId);
        if (highGasEstimate > info.maxGasCost) {
            return 0;
        } else {
            uint256 totalWeiPerMethod = info.maxGasPerAction * highGasEstimate;
            return ethBalances[targetAddress] / totalWeiPerMethod;
        }
    }

    /// @dev Orchestrator calls this function in order to reimburse tx.origin for method gas.
    ///      First it checks that all parameters are correct (gas price isn't too high),
    ///      And then it returns as much gas as is available to use in the transaction.
    ///      Note that this function will revert if the gas price is too high for the strategy.
    ///      This should be checked by the keeper beforehand.
    /// @param _targetAddress Address actions will be performed on, and address paying gas for those actions.
    /// @return gasAvailable (representing amount of gas available per Method).
    function gasAvailableForTransaction(
        address _targetAddress
    ) external view returns (uint256) {
        // Get gas info
        IVaultRegistry.VaultData memory vaultInfo = vaultRegistry
            .getVaultDetails(_targetAddress);
        IStrategyRegistry.RegisteredStrategy memory info = strategyRegistry
            .getRegisteredStrategy(vaultInfo.tokenId);

        // Ensure requested gas use is acceptable.
        // wei / gas must be less than maxGasCost,
        // and GasVault must have enough ether allotted to pay for action.
        require(tx.gasprice <= info.maxGasCost, "Gas too expensive.");

        // Represents gas available per action. Gas cost of all methods must be <= this.
        uint256 gasAvailable = info.maxGasPerAction;
        require(
            ethBalances[_targetAddress] >= tx.gasprice * gasAvailable,
            "Insufficient ether deposited"
        );

        // Return gas available
        return gasAvailable;
    }

    /// @dev Note that keepers still have to pull their gas from the GasVault in order
    ///      to truly be reimbursed--until then the ETH is just sitting in the GasVault.
    /// @param targetAddress The address which the action was performed upon.
    ///                      The reimbursement will come from its gas fund.
    /// @param originalGas How much gas there was at the start of the action (before any action was called)
    /// @param jobHash The hash of the job which was performed.
    ///                All vaults other than DynamicJobs can only have one job,
    ///                so in this case jobHash will just be actionHash.
    function reimburseGas(
        address targetAddress,
        uint256 originalGas,
        bytes32 jobHash
    ) external onlyOrchestrator {
        // Calculate reimbursement amount
        uint256 gasUsed = originalGas - gasleft();
        uint256 ethUsed = (gasUsed * tx.gasprice);

        // Distribute funds
        ethBalances[targetAddress] -= ethUsed;
        unchecked {
            ethBalances[tx.origin] += ethUsed;
        }
        emit EtherUsed(targetAddress, ethUsed, jobHash);
    }
}

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

import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorSettingsUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/compatibility/GovernorCompatibilityBravoUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "./interfaces/IInternalGovernance.sol";

contract InternalGovernance is
    IInternalGovernance,
    Initializable,
    GovernorUpgradeable,
    GovernorSettingsUpgradeable,
    GovernorCompatibilityBravoUpgradeable,
    GovernorVotesUpgradeable,
    GovernorVotesQuorumFractionUpgradeable,
    GovernorTimelockControlUpgradeable,
    OwnableUpgradeable,
    UUPSUpgradeable
{
    address private steerTimelock;
    mapping(address => bool) public hasVotingPower;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    function initialize(
        ERC20VotesUpgradeable _token,
        TimelockControllerUpgradeable _timelock,
        address _steerTimeLock,
        address[] calldata _voters
    ) public initializer {
        __Governor_init("SteerGovernance");
        __GovernorSettings_init(
            3600 /* 12 Hours of voting delay*/,
            3600 /* 12 Hours of voting period*/,
            100e18 /* Voters need 100 tokens to vote */
        );
        __GovernorCompatibilityBravo_init();
        __GovernorVotes_init(_token);
        __GovernorVotesQuorumFraction_init(2 /* 2% */);
        __GovernorTimelockControl_init(_timelock);
        __Ownable_init();
        __UUPSUpgradeable_init();
        steerTimelock = _steerTimeLock;
        giveVotingPowerDuringDeployment(_voters);
    }

    function _authorizeUpgrade(
        address newImplementation
    ) internal override onlyOwner {}

    function votingDelay()
        public
        view
        override(IGovernorUpgradeable, GovernorSettingsUpgradeable)
        returns (uint256)
    {
        return super.votingDelay();
    }

    function votingPeriod()
        public
        view
        override(IGovernorUpgradeable, GovernorSettingsUpgradeable)
        returns (uint256)
    {
        return super.votingPeriod();
    }

    function proposalThreshold()
        public
        view
        override(GovernorUpgradeable, GovernorSettingsUpgradeable)
        returns (uint256)
    {
        return super.proposalThreshold();
    }

    function quorum(
        uint256 blockNumber
    )
        public
        view
        override(IGovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable)
        returns (uint256)
    {
        return super.quorum(blockNumber);
    }

    function getVotes(
        address account,
        uint256 blockNumber
    )
        public
        view
        override(IGovernorUpgradeable, GovernorVotesUpgradeable)
        returns (uint256)
    {
        return super.getVotes(account, blockNumber);
    }

    function state(
        uint256 proposalId
    )
        public
        view
        override(
            GovernorUpgradeable,
            IGovernorUpgradeable,
            GovernorTimelockControlUpgradeable
        )
        returns (ProposalState)
    {
        return super.state(proposalId);
    }

    /**
     * @dev propose a new governance action.
     * @param targets the target address of each proposed action.
     * @param values the ETH value of each proposed action.
     * @param calldatas the calldata of each proposed action.
     * @param description the description of this governance action.
     */
    function propose(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description
    )
        public
        override(
            GovernorUpgradeable,
            GovernorCompatibilityBravoUpgradeable,
            IGovernorUpgradeable
        )
        returns (uint256)
    {
        require(
            hasVotingPower[msg.sender],
            "You don't have power to make proposal"
        );
        return super.propose(targets, values, calldatas, description);
    }

    /**
     * @dev propose a new governance action, with signatures from other signers
     * @param targets the target address of each proposed action.
     * @param signatures extra signatures for bravo governance
     * @param values the ETH value of each proposed action.
     * @param calldatas the calldata of each proposed action.
     * @param description the description of this governance action.
     */
    function propose(
        address[] memory targets,
        uint256[] memory values,
        string[] memory signatures,
        bytes[] memory calldatas,
        string memory description
    )
        public
        override(GovernorCompatibilityBravoUpgradeable)
        returns (uint256)
    {
        require(
            hasVotingPower[msg.sender],
            "You don't have power to make proposal"
        );
        return
            super.propose(targets, values, signatures, calldatas, description);
    }

    /*
     * @dev vote on a proposal handler
     * @param proposalId the ID of this proposal
     * @param account account that is voting
     * @param support is the vote being cast--0 for against, 1 for for, 2 for abstain.
     * @param reason is the reason for the vote being cast.
     * @dev call castVote to actually access this function.
     */
    function _castVote(
        uint256 proposalId,
        address account,
        uint8 support,
        string memory reason
    ) internal override(GovernorUpgradeable) returns (uint256) {
        require(
            hasVotingPower[msg.sender],
            "You don't have power to cast vote"
        );
        return super._castVote(proposalId, account, support, reason);
    }

    function _execute(
        uint256 proposalId,
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    )
        internal
        override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
    {
        super._execute(
            proposalId,
            targets,
            values,
            calldatas,
            descriptionHash
        );
    }

    function _cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    )
        internal
        override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
        returns (uint256)
    {
        require(hasVotingPower[msg.sender], "You don't have power to cancel");
        return super._cancel(targets, values, calldatas, descriptionHash);
    }

    function _executor()
        internal
        view
        override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
        returns (address)
    {
        return super._executor();
    }

    function supportsInterface(
        bytes4 interfaceId
    )
        public
        view
        override(
            GovernorUpgradeable,
            IERC165Upgradeable,
            GovernorTimelockControlUpgradeable
        )
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    /// @dev Use this function to give the ability to vote in the proposals made in this contract.
    /// @dev Only Steer Governance Timelock can call this function.
    /// @param _recipients is the array of addresses that should be given the ability to vote.
    function giveVotingPower(address[] calldata _recipients) external {
        require(msg.sender == steerTimelock, "Steer Governance");

        uint256 recipientCount = _recipients.length;
        for (uint256 i; i != recipientCount; ++i) {
            hasVotingPower[_recipients[i]] = true;
        }

        emit VotingPowerGiven(_recipients);
    }

    function giveVotingPowerDuringDeployment(
        address[] calldata _recipients
    ) internal {
        uint256 recipientCount = _recipients.length;
        for (uint256 i; i != recipientCount; ++i) {
            hasVotingPower[_recipients[i]] = true;
        }

        emit VotingPowerGiven(_recipients);
    }

    /// @dev Use this function to remove the ability to vote in the proposals made in this contract.
    /// @dev Only Steer Governance Timelock can call this function.
    /// @param _recipients is the array of addresses whose ability to vote should be removed.
    function removeVotingPower(address[] calldata _recipients) external {
        require(msg.sender == steerTimelock, "Steer Governance");

        uint256 recipientCount = _recipients.length;
        for (uint256 i; i != recipientCount; ++i) {
            hasVotingPower[_recipients[i]] = false;
        }

        emit VotingPowerRemoved(_recipients);
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import '@cryptoalgebra/integral-core/contracts/libraries/FullMath.sol';
import '@cryptoalgebra/integral-core/contracts/libraries/Constants.sol';

/// @title Liquidity amount functions
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license:
/// https://github.com/Uniswap/v3-periphery
library LiquidityAmounts {
    /// @notice Downcasts uint256 to uint128
    /// @param x The uint258 to be downcasted
    /// @return y The passed value, downcasted to uint128
    function toUint128(uint256 x) private pure returns (uint128 y) {
        require((y = uint128(x)) == x);
    }

    /// @notice Computes the amount of liquidity received for a given amount of token0 and price range
    /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount0 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount0(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, Constants.Q96);
        unchecked {
            return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96));
        }
    }

    /// @notice Computes the amount of liquidity received for a given amount of token1 and price range
    /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount1 The amount1 being sent in
    /// @return liquidity The amount of returned liquidity
    function getLiquidityForAmount1(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        unchecked {
            return toUint128(FullMath.mulDiv(amount1, Constants.Q96, sqrtRatioBX96 - sqrtRatioAX96));
        }
    }

    /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
    /// pool prices and the prices at the tick boundaries
    /// @param sqrtRatioX96 A sqrt price representing the current pool prices
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param amount0 The amount of token0 being sent in
    /// @param amount1 The amount of token1 being sent in
    /// @return liquidity The maximum amount of liquidity received
    function getLiquidityForAmounts(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
            uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);

            liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
        } else {
            liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
        }
    }

    /// @notice Computes the amount of token0 for a given amount of liquidity and a price range
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount0 The amount of token0
    function getAmount0ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0) {
        unchecked {
            if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

            return
                FullMath.mulDiv(
                    uint256(liquidity) << Constants.RESOLUTION,
                    sqrtRatioBX96 - sqrtRatioAX96,
                    sqrtRatioBX96
                ) / sqrtRatioAX96;
        }
    }

    /// @notice Computes the amount of token1 for a given amount of liquidity and a price range
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount1 The amount of token1
    function getAmount1ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        unchecked {
            return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, Constants.Q96);
        }
    }

    /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current
    /// pool prices and the prices at the tick boundaries
    /// @param sqrtRatioX96 A sqrt price representing the current pool prices
    /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary
    /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary
    /// @param liquidity The liquidity being valued
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function getAmountsForLiquidity(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0, uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
            amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
        } else {
            amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        }
    }
}

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

import "./FenixBaseLiquidityManager.sol";

contract FenixMultiPositionLiquidityManager is FenixBaseLiquidityManager {
    using SafeERC20 for IERC20;

    // Types

    /// @dev The vault's position data. At any given moment this represents
    ///      all active positions inside the pool.
    ///      Each lowerTick is the lower bound of the position at that index.
    ///      Each upperTick is the upper bound of the position at that index.
    ///      Each relativeWeight is the relative weight of the position at that index,
    ///      relative to the other positions.
    ///      So for example if LiquidityPositions is
    ///        {
    ///            lowerTicks: [0, 20, 40],
    ///            upperTicks: [10, 30, 50],
    ///            relativeWeights: [1, 2, 3]
    ///        }
    ///        then that means the vault has 3 positions:
    ///            1. 0-10 with relative weight 1
    ///            2. 20-30 with relative weight 2
    ///            3. 40-50 with relative weight 3
    struct LiquidityPositions {
        int24 lowerTick;
        int24 upperTick;
        uint16 relativeWeight;
    }

    struct TendLiquidityPositions {
        int24[] lowerTick;
        int24[] upperTick;
        uint16[] relativeWeight;
    }
    // Storage
    LiquidityPositions[] internal positions;

    // External Functions

    /// @dev Get current positions held by the vault
    /// This function is used for mainly read calls so looping shouldn't be a problem
    function getPositions()
        external
        view
        returns (int24[] memory, int24[] memory, uint16[] memory)
    {
        uint256 length = positions.length;
        TendLiquidityPositions memory oldPositions;

        // Initialize the dynamic arrays with the correct length
        oldPositions.lowerTick = new int24[](length);
        oldPositions.upperTick = new int24[](length);
        oldPositions.relativeWeight = new uint16[](length);

        for (uint256 i; i != length; ++i) {
            oldPositions.lowerTick[i] = positions[i].lowerTick;
            oldPositions.upperTick[i] = positions[i].upperTick;
            oldPositions.relativeWeight[i] = positions[i].relativeWeight;
        }

        return (
            oldPositions.lowerTick,
            oldPositions.upperTick,
            oldPositions.relativeWeight
        );
    }

    /// @dev Internal function to pull funds from pool.
    ///      Update positions if necessary, then deposit funds into pool.
    ///      Reverts if the vault does not own any liquidity or tokens.
    ///      newPositions requirements:
    ///        Each lowerTick must be lower than its corresponding upperTick
    ///        Each lowerTick must be greater than or equal to the tick min (-887272)
    ///        Each upperTick must be less than or equal to the tick max (887272)
    ///        All lowerTicks and upperTicks must be divisible by the pool tickSpacing--
    ///      A 0.05% fee pool has tick spacing of 10, 0.3% has tick spacing 60.
    ///      And 1% has tick spacing 200.
    /// @param totalWeight The share of liquidity we want deposited, multiplied by 10,000.
    ///           A value of 10,000 means we want to deposit all tokens into Algebra.
    ///           A value of 0 means we just want all the liquidity out.
    ///           Note that values above 10,000 are not explicitly prohibited
    ///           but will generally cause the tend to fail.
    /// @param newPositions The info of each new position to be set.
    ///           newPositions.lowerTick[] is an array, in order, of the lower ticks of each position
    ///           newPositions.upperTick[] is an array, in order, of the upper ticks of each position
    ///           newPositions.relativeWeight[] is an array, in order, of the relative weights of each position
    ///           So if for example newPositions is called with lowerTick = [-120, 0],
    ///           upperTick = [-60, 60], and relativeWeight = [1, 5],
    ///           then the positions would be -120 to -60 with a weight of 1,
    ///           and 0 to 60 with a weight of 5.
    ///           The weight differences roughly correspond to what
    ///           proportion of the liquidity is added to each position.
    /// @param timeSensitiveData Encoded info of the swapAmount and sqrtPriceLimitX96.
    ///           It must be encoded as bytes so that it the data is placed after
    ///           the newPositions, which is also a dynamic data type.
    ///        timeSensitiveData.swapAmount: the amount to be swapped from one token to another this tend. zeroForOne if positive, oneForZero if negative.
    ///        timeSensitiveData.sqrtPriceLimitX96: the slippage limit of the swap. Protections elsewhere prevent extreme slippage if the keeper calling this
    ///        function is malicious, but this is the first line of defense against MEV attacks.
    struct TendCache {
        int256 swapAmount;
        uint160 sqrtPriceLimitX96;
        uint160 sqrtPriceX96;
        int24 currentTick;
        uint256 newPosLength;
        uint256 balance0;
        uint256 balance1;
        bool zeroForOne;
    }

    function tend(
        uint256 totalWeight,
        TendLiquidityPositions memory newPositions,
        bytes calldata timeSensitiveData
    ) external onlyRole(MANAGER_ROLE) whenNotPaused {
        TendCache memory tendCache;
        require(totalWeight <= TOTAL_WEIGHT_MAX);
        (tendCache.swapAmount, tendCache.sqrtPriceLimitX96) = abi.decode(
            timeSensitiveData,
            (int256, uint160)
        );

        // Get current pool state
        (tendCache.sqrtPriceX96, tendCache.currentTick, , , , ) = pool
            .globalState();

        // currentTick must be close enough to TWAP tick to avoid MEV exploit
        // This is essentially a way to prevent a flashloan attack
        // even if sqrtPriceLimit is set incorrectly.
        _checkVolatility(tendCache.currentTick);

        // Withdraw liquidity from Algebra pool by passing in 1 and 1
        // (indicating we're withdrawing 100% of liquidity)
        _burnAndCollect(1, 1);

        // Update positions if desired. If newPositions is empty,
        // we'll just continue with the old positions instead.
        tendCache.newPosLength = newPositions.lowerTick.length;
        if (tendCache.newPosLength > 0) {
            delete positions;
            for (uint256 i = 0; i < tendCache.newPosLength; i++) {
                LiquidityPositions memory newPosition = LiquidityPositions({
                    lowerTick: newPositions.lowerTick[i],
                    upperTick: newPositions.upperTick[i],
                    relativeWeight: newPositions.relativeWeight[i]
                });

                positions.push(newPosition);
            }
        }

        // Perform a swap if desired.
        if (tendCache.swapAmount != 0) {
            tendCache.zeroForOne = tendCache.swapAmount > 0;
            require(tendCache.swapAmount != type(int256).min);
            swapCallBackProtection = true;
            pool.swap(
                address(this),
                tendCache.zeroForOne,
                tendCache.zeroForOne
                    ? tendCache.swapAmount
                    : -tendCache.swapAmount,
                tendCache.sqrtPriceLimitX96,
                ""
            );

            // Update sqrtPriceX96; it will have moved due to the swap
            (tendCache.sqrtPriceX96, , , , , ) = pool.globalState();
        }

        tendCache.balance0 = _getBalance0();
        tendCache.balance1 = _getBalance1();

        emit Snapshot(
            tendCache.sqrtPriceX96,
            tendCache.balance0,
            tendCache.balance1,
            totalSupply()
        );

        // Create new positions in Algebra
        if (totalWeight > 0) {
            _setBins(
                tendCache.sqrtPriceX96,
                // balance0 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance0, totalWeight, DIVISOR),
                // balance1 adjusted by totalWeight
                FullMath.mulDiv(tendCache.balance1, totalWeight, DIVISOR),
                tendCache.swapAmount
            );
        }
    }

    // Public Functions

    /// @dev burns each vault position which contains liquidity, updating fees owed to that position.
    ///      Call this before calling getTotalAmounts if total amounts must include fees.
    ///      There's a function in the periphery to do so through a static call.
    function poke() public override {
        LiquidityPositions[] memory _positions = positions;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            // Get position liquidity so that we can ignore this position if it has 0 liquidity.
            (uint256 positionLiquidity, , , , ) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            // If position has liquidity, update fees owed.
            if (positionLiquidity > 0) {
                pool.burn(
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    0,
                    ""
                );
            }
        }
    }

    /// @dev Calculates the vault's total holdings of token0 and token1.
    ///      in other words, how much of each token the vault would hold if it withdrew
    ///      all its liquidity from Algebra.
    ///      This function DOES NOT include fees earned since the last burn.
    ///      To include fees, first poke() and then call getTotalAmounts.
    ///      There's a function inside the periphery to do so.
    function getTotalAmounts()
        public
        view
        override
        returns (uint256 total0, uint256 total1)
    {
        // Start with tokens currently held inside the vault
        total0 = _getBalance0();
        total1 = _getBalance1();

        LiquidityPositions[] memory _positions = positions;
        (uint160 sqrtPriceX96, , , , , ) = pool.globalState();

        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 feeSubtract = FEE_DIVISOR - totalFees;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            (uint256 liquidity, , , uint256 fees0, uint256 fees1) = _position(
                _positions[i].lowerTick,
                _positions[i].upperTick
            );

            (uint256 amt0, uint256 amt1) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(liquidity)
                );

            // Directly adding to totals, removing intermediate total fee variables
            total0 += amt0 + FullMath.mulDiv(fees0, feeSubtract, FEE_DIVISOR);
            total1 += amt1 + FullMath.mulDiv(fees1, feeSubtract, FEE_DIVISOR);
        }
    }

    // Internal Functions

    /// @dev Given desired positions, desired relative weights, and a current token amount,
    ///      This function deposits as much liquidity as possible into each position
    ///      while respecting relative weights.
    /// @param sqrtPriceX96 The current sqrtPriceX96 of the pool
    /// @param t0ToDeposit The vault's current balance of token0 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param t1ToDeposit The vault's current balance of token1 ready to be deposited
    ///                 (excluding steer and strategist fees)
    /// @param swapAmount The amount to be swapped from one token to another this tend.
    ///                   zeroForOne if positive, oneForZero if negative.
    ///                   Here it is mainly used to determine which direction the swap was,
    ///                   so that we can check whether the swap was too large.
    function _setBins(
        uint160 sqrtPriceX96,
        uint256 t0ToDeposit,
        uint256 t1ToDeposit,
        int256 swapAmount
    ) internal {
        //@todo check the updated code
        LiquidityPositions[] memory _positions = positions;

        // Get relative amounts of t0 and t1 for each position.
        ///    Temporary array built to hold the weights of each token in each liquidity position.
        ///      t0Weights[0] = Token 0 weight in the first liquidity position, multiplied by PRECISION.
        ///      t1 weight in that position can be calculated using PRECISION * total bin weight - t0 weight.
        uint256[] memory positionT0Requested;
        uint256[] memory positionT1Requested;
        uint256 totalT0Requested;
        uint256 totalT1Requested;
        uint256 positionCount = _positions.length;
        positionT0Requested = new uint256[](positionCount);
        positionT1Requested = new uint256[](positionCount);

        // For each bin, figure out how much of the bin will be in token0,
        // and how much will be in token1.
        // Set weights accordingly--if a bin's weight is 10, and nine tenths of its value
        // will be in token0, then its token0 weight will be 9 and its token1 weight will be 1.
        for (uint256 i; i != positionCount; ++i) {
            // For each position, find amount0Wanted and amount1Wanted
            // given a liquidity of PRECISION * relativeWeight.
            if (i >= 1) {
                require(
                    _positions[i - 1].lowerTick < _positions[i].lowerTick &&
                        _positions[i - 1].upperTick < _positions[i].upperTick
                );
            }
            (uint256 amount0Wanted, uint256 amount1Wanted) = LiquidityAmounts
                .getAmountsForLiquidity(
                    sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                    TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                    uint128(PRECISION * _positions[i].relativeWeight)
                    // No safecast here--an overflow will lead to an incorrect number,
                    // which will either (usually) revert, or cause a harmless liquidity miscalculation.
                );
            // Record amt0Delta and amt1Delta for this position
            positionT0Requested[i] = amount0Wanted;
            positionT1Requested[i] = amount1Wanted;

            // Add amt0Delta and amt1Delta to totalT0Requested and totalT1Requested
            totalT0Requested += amount0Wanted;
            totalT1Requested += amount1Wanted;
        }

        // Now add liquidity to those bins based on their weights vs total token weights.
        // If relativeWeights have a bad input (such as a weight of 0, or a very high weight,
        // in one of the positions) the below code will revert in some cases, proceed in others.
        // The result will not be correct but all that a bad input can do
        // is cause a revert or cause less than 100% of liquidity to be deployed.
        for (uint256 i; i != positionCount; ++i) {
            // Liquidity to deposit for this position is calculated using _liquidityForAmounts
            // and the calculated tokens to deposit for the position.
            // Set token amounts for this position
            uint256 positionT0Amount = totalT0Requested > 0
                ? FullMath.mulDiv(
                    positionT0Requested[i],
                    t0ToDeposit,
                    totalT0Requested
                )
                : 0;
            uint256 positionT1Amount = totalT1Requested > 0
                ? FullMath.mulDiv(
                    positionT1Requested[i],
                    t1ToDeposit,
                    totalT1Requested
                )
                : 0;

            uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtPriceX96,
                TickMath.getSqrtRatioAtTick(_positions[i].lowerTick),
                TickMath.getSqrtRatioAtTick(_positions[i].upperTick),
                positionT0Amount,
                positionT1Amount
            );

            // Create the position inside the pool.
            if (liquidity > 0) {
                mintCallBackProtection = true;
                pool.mint(
                    address(this),
                    address(this),
                    _positions[i].lowerTick,
                    _positions[i].upperTick,
                    liquidity,
                    ""
                );
            }
        }

        // Check post-mint balances.
        // We need to check that less than 5% of the TO token (i.e. the token we swapped into) remains post-mint.
        // Having the right liquidity ratio is extremely valuable,
        // but the main thing we're protecting against here is dynamic data that swaps more than it should.

        // As an example, assume a malicious keeper has flashloaned the Algebra pool
        // into a very bad exchange rate before handling the swap.
        // If exchange rate is extremely high token1PerToken0, exploiter will want to swap from token1 to token0
        // (going the other way just helps the liquidity manager)
        // But the positions will be entirely in token1.
        // The check below ensures that no more than 5% of the total contract token0 remains undeposited,
        // a reliable indicator that the correct amount of token1 was swapped.

        // This combined with the TWAP check makes flashloan exploits extremely difficult for a single keeper.

        // If swapAmount > 0, that means zeroForOne. Otherwise, oneForZero.
        // No overflow checks here because some kind of overflow exploit is
        // both implausible and would just cause a revert.
        if (swapAmount > 0) {
            // Require that at least 95% of t1 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance1() < (t1ToDeposit * FIVE) / DIVISOR100, "S");
        } else if (swapAmount < 0) {
            // Require that at least 95% of t0 has been deposited
            // (ensuring that swap amount wasn't too great)
            require(_getBalance0() < (t0ToDeposit * FIVE) / DIVISOR100, "S");
        }
    }

    /// @dev Withdraws liquidity from all positions, allocating fees correctly in the process.
    /// @param shares LP shares being withdrawn
    /// @param totalShares total # of LP tokens in the vault
    /// @return t0 Token0 earned from burned liquidity + fees.
    ///            Only includes burned + fees corresponding to LP shares being withdrawn (100% if tend)
    /// @return t1 Token1 earned from burned liquidity + fees
    function _burnAndCollect(
        uint256 shares,
        uint256 totalShares
    ) internal override returns (uint256 t0, uint256 t1) {
        // First, fetch current positions, Only tend() and withdraw() call this function,
        // and neither uses these positions elsewhere (tend uses updated ones).
        LiquidityPositions[] memory _positions = positions;

        // For each position, burn() and then withdraw correct amount of liquidity.
        uint256 fees0;
        uint256 fees1;
        uint256 positionCount = _positions.length;
        for (uint256 i; i != positionCount; ++i) {
            int24 lowerTick = _positions[i].lowerTick;
            int24 upperTick = _positions[i].upperTick;

            uint128 liquidityToBurn;

            // Get position liquidity. If we don't want all of it,
            // here is where we specify how much we want
            // (liquidity * fraction of pool which is being withdrawn)
            // Slightly tortured logic here due to stack management
            {
                (uint256 totalPositionLiquidity, , , , ) = _position(
                    lowerTick,
                    upperTick
                );

                liquidityToBurn = uint128(
                    FullMath.mulDiv(
                        totalPositionLiquidity,
                        shares,
                        totalShares
                    )
                );
            }

            // amountsOwed are always pulled with liquidity in this contract,
            // so if position liquidity is 0, no need to withdraw anything.
            if (liquidityToBurn > 0) {
                // Amount burned in each position.
                // Corresponds to amount of liquidity withdrawn (i.e. doesn't include fees).
                (uint256 posBurned0, uint256 posBurned1) = pool.burn(
                    lowerTick,
                    upperTick,
                    liquidityToBurn,
                    ""
                );

                // Collect all owed tokens including earned fees
                (uint256 collect0, uint256 collect1) = pool.collect(
                    address(this),
                    lowerTick,
                    upperTick,
                    type(uint128).max,
                    type(uint128).max
                );

                /*
                 * Add liquidity burned to t0 and t1--this is already proportional to amt being withdrawn
                 * No need to check for overflow--values come from Algebra, and a total burn greater than 2^256 - 1 would represent burning more than a token's total supply.
                 * Technically possible given a malicious token, but a malicious token can already steal vault holdings due to the nature of Algebra
                    (just have the vault deposit all tokens, then mint an arbitrary amount of the malicious token and swap for the real token)
                */
                t0 += posBurned0;
                t1 += posBurned1;

                // Fees earned by liquidity inside Algebra = collected - burned.
                // First we allocate some to steer and some to strategist.
                // The remainder is the fees earned by LPs.
                // So after that we add remainder * shares / totalShares,
                // and that gives us t0 and t1 allocated to whatever's being withdrawn.

                // Since burned will never be greater than collected, no need to check for underflow here.
                // Since collect values were originally uint128's, no need to check for overflow either. It would take ~2^128 max additions to overflow.
                fees0 += collect0 - posBurned0;
                fees1 += collect1 - posBurned1;
            }
        }
        // Emit fee info
        emit FeesEarned(fees0, fees1);
        IFeeManager.Fee[] memory fees = IFeeManager(feeManager).getFees(
            address(this)
        );
        uint256 totalFees = IFeeManager(feeManager).vaultTotalFees(
            address(this)
        );
        uint256 totalCut0;
        uint256 totalCut1;
        if (fees0 > 0)
            totalCut0 = FullMath.mulDiv(fees0, totalFees, FEE_DIVISOR);
        if (fees1 > 0)
            totalCut1 = FullMath.mulDiv(fees1, totalFees, FEE_DIVISOR);
        totalFees0 += totalCut0; //Add to total Fees
        totalFees1 += totalCut1; //Add to total Fees
        // Subtract fees going to strategist/steer from fees going to vault
        fees0 -= totalCut0;
        // Subtract fees going to strategist/steer from fees going to vault
        fees1 -= totalCut1;
        for (uint256 j; j != fees.length; ++j) {
            // Increase fees
            accruedFees0[fees[j].feeIdentifier] =
                accruedFees0[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut0, fees[j].feeValue, FEE_DIVISOR);

            // Increase fees
            accruedFees1[fees[j].feeIdentifier] =
                accruedFees1[fees[j].feeIdentifier] +
                FullMath.mulDiv(totalCut1, fees[j].feeValue, FEE_DIVISOR);
        }
        // Add fees earned by burned position to burned amount
        t0 += FullMath.mulDiv(fees0, shares, totalShares);
        t1 += FullMath.mulDiv(fees1, shares, totalShares);
    }
}

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

import "./interfaces/IStrategyRegistry.sol";

// Proxy Support
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

// Access Control
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";

// 721 Support
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721PausableUpgradeable.sol";

/// @title A registry for strategies
/// @author Steer Protocol
/// @dev All strategies are registered through this contract.
/// @dev This is where strategy bundles are stored as well as the offline data needed to decode parameters stored on a vault.
contract StrategyRegistry is
    Initializable,
    ERC721Upgradeable,
    ERC721URIStorageUpgradeable,
    ERC721EnumerableUpgradeable,
    ERC721PausableUpgradeable,
    AccessControlUpgradeable,
    UUPSUpgradeable,
    OwnableUpgradeable
{
    // Library setup
    using CountersUpgradeable for CountersUpgradeable.Counter;

    string internal _baseTokenURI;

    // Mapping for pulling strategy details from the registry by the hash of the ipfs cid
    //    CID => RegisteredStrategy
    mapping(string => IStrategyRegistry.RegisteredStrategy) public strategies;

    // Mapping for pulling strategy ipfs cid by the ERC721 tokenId associated
    //   ERC721 tokenId => CID
    mapping(uint256 => string) public tokenIdToExecBundle;

    // Counter to keep track of totalSupply
    CountersUpgradeable.Counter public _tokenIdTracker;

    // Set up roles
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");

    // Gas configuration limits
    uint256 public maxMaxGasPerAction; // Max allowable maxGasPerAction. Attempting to set a maxGasPerAction higher than this will revert.

    // Misc constants
    bytes32 public constant hashedEmptyString = keccak256("");

    event StrategyCreated(
        address indexed owner,
        uint256 indexed tokenId,
        string name //IPFS identifier of execution bundle
    );

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer() {}

    function initialize(string memory registry) public initializer {
        // Initializers
        __Context_init();
        __Ownable_init();
        __UUPSUpgradeable_init();
        __ERC165_init();
        __AccessControl_init();
        __ERC721_init("Steer Strategy", "STR_SRTGY");
        __ERC721Enumerable_init();
        __Pausable_init();
        __ERC721Pausable_init();
        __ERC721URIStorage_init();

        // Assign roles
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(GOVERNANCE_ROLE, _msgSender());

        // Setup the registry base url for the offchain protocol
        // this is used to create namespaced networks
        _baseTokenURI = registry;
        maxMaxGasPerAction = 15_000_000; // 15 million by default
    }

    /// @dev Create NFT for execution bundle
    /// @param strategyName The name of the strategy.
    /// @param execBundle The IPFS reference of the execution bundle.
    /// @param maxGasCost The maximum gas cost of the strategy.
    /// @param maxGasPerAction The maximum gas per action of the strategy, in terms of wei / gas.
    /// @return newStrategyTokenId as the token id of the new NFT.
    function createStrategy(
        address strategyCreator,
        string calldata strategyName,
        string calldata execBundle,
        uint128 maxGasCost,
        uint128 maxGasPerAction
    ) external returns (uint256 newStrategyTokenId) {
        // Check if the strategy is already registered
        // This occurs when the bundle has the same CID as a previously registered bundle
        bytes32 hashOfExecBundle = keccak256(abi.encodePacked(execBundle));
        require(hashOfExecBundle != hashedEmptyString, "Empty");
        require(
            keccak256(abi.encodePacked(strategies[execBundle].execBundle)) !=
                hashOfExecBundle,
            "Exists"
        );
        // Validate gas config
        require(
            maxGasPerAction <= maxMaxGasPerAction,
            "maxGasPerAction too high"
        );

        // Mint a new token to the current sender
        newStrategyTokenId = mint(strategyCreator, execBundle);

        // Utilizing the CID of the bundle we map the CID to a struct of RegisteredStrategy
        // We use the bundle hash instead of the token ID because this is helpful for the offchain protocol
        strategies[execBundle] = IStrategyRegistry.RegisteredStrategy({
            id: newStrategyTokenId,
            name: strategyName,
            owner: strategyCreator,
            execBundle: execBundle,
            maxGasCost: maxGasCost,
            maxGasPerAction: maxGasPerAction
        });

        // To help with enumeration we also map the token ID to the CID
        tokenIdToExecBundle[newStrategyTokenId] = execBundle;

        // Emit StrategyCreated event once a strategy is created
        emit StrategyCreated(
            strategyCreator,
            newStrategyTokenId,
            strategyName
        );
    }

    /// @dev Get the base URI
    /// @return The base URI of the registry
    /// @dev This is an internal function
    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }

    /**
     * @dev Get the base URI
     * @return The base URI of the registry
     */
    function baseURI() external view returns (string memory) {
        return _baseURI();
    }

    /**
     * @dev Creates a new token for `to`. Its token ID will be automatically
     * assigned (and available on the emitted {IERC721-Transfer} event), and the token
     * URI autogenerated based on the base URI passed at construction.
     *
     * See {ERC721-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(
        address recipient,
        string calldata bundle
    ) private returns (uint256) {
        uint256 newStrategyId = _tokenIdTracker.current();
        _mint(recipient, newStrategyId);
        _setTokenURI(newStrategyId, bundle);
        _tokenIdTracker.increment();
        return newStrategyId;
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC721Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public {
        require(
            hasRole(PAUSER_ROLE, _msgSender()),
            "ERC721PresetMinterPauserAutoId: must have pauser role to pause"
        );
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC721Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public {
        require(
            hasRole(PAUSER_ROLE, _msgSender()),
            "ERC721PresetMinterPauserAutoId: must have pauser role to unpause"
        );
        _unpause();
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    )
        internal
        override(
            ERC721Upgradeable,
            ERC721EnumerableUpgradeable,
            ERC721PausableUpgradeable
        )
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(
        bytes4 interfaceId
    )
        public
        view
        override(
            AccessControlUpgradeable,
            ERC721Upgradeable,
            ERC721EnumerableUpgradeable
        )
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    function tokenURI(
        uint256 tokenId
    )
        public
        view
        override(ERC721URIStorageUpgradeable, ERC721Upgradeable)
        returns (string memory)
    {
        return _baseURI();
    }

    function _burn(
        uint256 tokenId
    ) internal override(ERC721URIStorageUpgradeable, ERC721Upgradeable) {
        super._burn(tokenId);
    }

    function _authorizeUpgrade(address) internal override onlyOwner {}

    /// @dev Get the strategy details by tokenId
    /// @param tokenId The token id of the strategy
    /// @return The details of the strategy
    function getRegisteredStrategy(
        uint256 tokenId
    ) public view returns (IStrategyRegistry.RegisteredStrategy memory) {
        return strategies[tokenIdToExecBundle[tokenId]];
    }

    /// @dev Set the gas parameters for a given strategy
    /// @param _tokenId The token id of the strategy
    /// @param _maxGasCost The maximum gas cost of the strategy
    /// @param _maxGasPerAction The maximum gas per action of the strategy
    function setGasParameters(
        uint256 _tokenId,
        uint128 _maxGasCost,
        uint128 _maxGasPerAction
    ) external {
        // Only the owner of the strategy is the only one who can set the gas parameters
        require(
            msg.sender == ownerOf(_tokenId),
            "Only strategy owner can set gas parameters"
        );

        // Validate gas config
        require(
            _maxGasPerAction <= maxMaxGasPerAction,
            "maxGasPerAction too high"
        );

        // Retrieve the current strategy details
        IStrategyRegistry.RegisteredStrategy storage strategy = strategies[
            tokenIdToExecBundle[_tokenId]
        ];

        // Set the gas parameters
        strategy.maxGasCost = _maxGasCost;
        strategy.maxGasPerAction = _maxGasPerAction;
    }

    function setMaxMaxGasPerAction(
        uint256 _maxMaxGasPerAction
    ) external onlyOwner {
        require(_maxMaxGasPerAction >= 15_000_000, "Invalid");
        maxMaxGasPerAction = _maxMaxGasPerAction;
    }
}

File 175 of 177 : Constants.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.6;

library Constants {
    uint8 internal constant RESOLUTION = 96;
    uint256 internal constant Q96 = 0x1000000000000000000000000;
    uint256 internal constant Q128 = 0x100000000000000000000000000000000;
    // fee value in hundredths of a bip, i.e. 1e-6
    uint16 internal constant BASE_FEE = 100;
    int24 internal constant MAX_TICK_SPACING = 500;

    // max(uint128) / (MAX_TICK - MIN_TICK)
    uint128 internal constant MAX_LIQUIDITY_PER_TICK =
        191757638537527648490752896198553;

    uint32 internal constant MAX_LIQUIDITY_COOLDOWN = 1 days;
    uint8 internal constant MAX_COMMUNITY_FEE = 250;
    uint256 internal constant COMMUNITY_FEE_DENOMINATOR = 1000;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

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 onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        _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 making 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;
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.7.6;

import "./Constants.sol";

/// @title AdaptiveFee
/// @notice Calculates fee based on combination of sigmoids
library AdaptiveFee {
    // alpha1 + alpha2 + baseFee must be <= type(uint16).max
    struct Configuration {
        uint16 alpha1; // max value of the first sigmoid
        uint16 alpha2; // max value of the second sigmoid
        uint32 beta1; // shift along the x-axis for the first sigmoid
        uint32 beta2; // shift along the x-axis for the second sigmoid
        uint16 gamma1; // horizontal stretch factor for the first sigmoid
        uint16 gamma2; // horizontal stretch factor for the second sigmoid
        uint32 volumeBeta; // shift along the x-axis for the outer volume-sigmoid
        uint16 volumeGamma; // horizontal stretch factor the outer volume-sigmoid
        uint16 baseFee; // minimum possible fee
    }

    /// @notice Calculates fee based on formula:
    /// baseFee + sigmoidVolume(sigmoid1(volatility, volumePerLiquidity) + sigmoid2(volatility, volumePerLiquidity))
    /// maximum value capped by baseFee + alpha1 + alpha2
    function getFee(
        uint88 volatility,
        uint256 volumePerLiquidity,
        Configuration memory config
    ) internal pure returns (uint16 fee) {
        uint256 sumOfSigmoids = sigmoid(
            volatility,
            config.gamma1,
            config.alpha1,
            config.beta1
        ) + sigmoid(volatility, config.gamma2, config.alpha2, config.beta2);

        if (sumOfSigmoids > type(uint16).max) {
            // should be impossible, just in case
            sumOfSigmoids = type(uint16).max;
        }

        return
            uint16(
                config.baseFee +
                    sigmoid(
                        volumePerLiquidity,
                        config.volumeGamma,
                        uint16(sumOfSigmoids),
                        config.volumeBeta
                    )
            ); // safe since alpha1 + alpha2 + baseFee _must_ be <= type(uint16).max
    }

    /// @notice calculates a / (1 + e^( (ß-x) / ?))
    /// that is a sigmoid with a maximum value of a, x-shifted by ß, and stretched by ?
    /// @dev returns uint256 for fuzzy testing. Guaranteed that the result is not greater than alpha
    function sigmoid(
        uint256 x,
        uint16 g,
        uint16 alpha,
        uint256 beta
    ) internal pure returns (uint256 res) {
        if (x > beta) {
            x = x - beta;
            if (x >= 6 * uint256(g)) return alpha; // so x < 19 bits
            uint256 g8 = uint256(g) ** 8; // < 128 bits (8*16)
            uint256 ex = exp(x, g, g8); // < 155 bits
            res = (alpha * ex) / (g8 + ex); // in worst case: (16 + 155 bits) / 155 bits
            // so res <= alpha
        } else {
            x = beta - x;
            if (x >= 6 * uint256(g)) return 0; // so x < 19 bits
            uint256 g8 = uint256(g) ** 8; // < 128 bits (8*16)
            uint256 ex = g8 + exp(x, g, g8); // < 156 bits
            res = (alpha * g8) / ex; // in worst case: (16 + 128 bits) / 156 bits
            // g8 <= ex, so res <= alpha
        }
    }

    /// @notice calculates e^(x/g) * g^8 in a series, since (around zero):
    /// e^x = 1 + x + x^2/2 + ... + x^n/n! + ...
    /// e^(x/g) = 1 + x/g + x^2/(2*g^2) + ... + x^(n)/(g^n * n!) + ...
    function exp(
        uint256 x,
        uint16 g,
        uint256 gHighestDegree
    ) internal pure returns (uint256 res) {
        // calculating:
        // g**8 + x * g**7 + (x**2 * g**6) / 2 + (x**3 * g**5) / 6 + (x**4 * g**4) / 24 + (x**5 * g**3) / 120 + (x**6 * g^2) / 720 + x**7 * g / 5040 + x**8 / 40320

        // x**8 < 152 bits (19*8) and g**8 < 128 bits (8*16)
        // so each summand < 152 bits and res < 155 bits
        uint256 xLowestDegree = x;
        res = gHighestDegree; // g**8

        gHighestDegree /= g; // g**7
        res += xLowestDegree * gHighestDegree;

        gHighestDegree /= g; // g**6
        xLowestDegree *= x; // x**2
        res += (xLowestDegree * gHighestDegree) / 2;

        gHighestDegree /= g; // g**5
        xLowestDegree *= x; // x**3
        res += (xLowestDegree * gHighestDegree) / 6;

        gHighestDegree /= g; // g**4
        xLowestDegree *= x; // x**4
        res += (xLowestDegree * gHighestDegree) / 24;

        gHighestDegree /= g; // g**3
        xLowestDegree *= x; // x**5
        res += (xLowestDegree * gHighestDegree) / 120;

        gHighestDegree /= g; // g**2
        xLowestDegree *= x; // x**6
        res += (xLowestDegree * gHighestDegree) / 720;

        xLowestDegree *= x; // x**7
        res += (xLowestDegree * g) / 5040 + (xLowestDegree * x) / (40320);
    }
}

Settings
{
  "libraries": {},
  "metadata": {
    "useLiteralContent": true
  },
  "optimizer": {
    "enabled": true,
    "runs": 10
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"beacon","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"stateMutability":"payable","type":"receive"}]

608060405260405161090c38038061090c83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e5602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034c806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f060279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610247565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a0565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020b578251610204576101b385610055565b6102045760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610215565b610215838361021d565b949350505050565b81511561022d5781518083602001fd5b8060405162461bcd60e51b81526004016101fb91906102bc565b60006020828403121561025957600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028b578181015183820152602001610273565b8381111561029a576000848401525b50505050565b600082516102b2818460208701610270565b9190910192915050565b60208152600082518060208401526102db816040850160208701610270565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220c149cc587c459fdcb5b0b327a81fc3a237cb5f545f983075dc92d02083b7eab964736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564000000000000000000000000834a0ec5347be6e62a1e57a01f1250c7ddde617a00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000144246581f70000000000000000000000001c735adfe82133dda6a08b81b813e4d7aff3b32a0000000000000000000000001c735adfe82133dda6a08b81b813e4d7aff3b32a000000000000000000000000d9db26176dd2a905aaf9213581366a5bb3913573000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000203a662b0bd271a6ed5a60edfbd04bfce608fd360000000000000000000000002dca96907fde857dd3d816880a0df407eeb2d2f2000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000002ee000000000000000000000000000000000000000000000000000000000000002d00000000000000000000000000000000000000000000000000000000

Deployed Bytecode

0x60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f060279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610247565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a0565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020b578251610204576101b385610055565b6102045760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610215565b610215838361021d565b949350505050565b81511561022d5781518083602001fd5b8060405162461bcd60e51b81526004016101fb91906102bc565b60006020828403121561025957600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028b578181015183820152602001610273565b8381111561029a576000848401525b50505050565b600082516102b2818460208701610270565b9190910192915050565b60208152600082518060208401526102db816040850160208701610270565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220c149cc587c459fdcb5b0b327a81fc3a237cb5f545f983075dc92d02083b7eab964736f6c634300080c0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000834a0ec5347be6e62a1e57a01f1250c7ddde617a00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000144246581f70000000000000000000000001c735adfe82133dda6a08b81b813e4d7aff3b32a0000000000000000000000001c735adfe82133dda6a08b81b813e4d7aff3b32a000000000000000000000000d9db26176dd2a905aaf9213581366a5bb3913573000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000203a662b0bd271a6ed5a60edfbd04bfce608fd360000000000000000000000002dca96907fde857dd3d816880a0df407eeb2d2f2000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000002ee000000000000000000000000000000000000000000000000000000000000002d00000000000000000000000000000000000000000000000000000000

-----Decoded View---------------
Arg [0] : beacon (address): 0x834A0eC5347Be6E62A1E57A01F1250c7dddE617A
Arg [1] : data (bytes): 0x246581f70000000000000000000000001c735adfe82133dda6a08b81b813e4d7aff3b32a0000000000000000000000001c735adfe82133dda6a08b81b813e4d7aff3b32a000000000000000000000000d9db26176dd2a905aaf9213581366a5bb3913573000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000203a662b0bd271a6ed5a60edfbd04bfce608fd360000000000000000000000002dca96907fde857dd3d816880a0df407eeb2d2f2000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000002ee000000000000000000000000000000000000000000000000000000000000002d

-----Encoded View---------------
14 Constructor Arguments found :
Arg [0] : 000000000000000000000000834a0ec5347be6e62a1e57a01f1250c7ddde617a
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000040
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000144
Arg [3] : 246581f70000000000000000000000001c735adfe82133dda6a08b81b813e4d7
Arg [4] : aff3b32a0000000000000000000000001c735adfe82133dda6a08b81b813e4d7
Arg [5] : aff3b32a000000000000000000000000d9db26176dd2a905aaf9213581366a5b
Arg [6] : b391357300000000000000000000000000000000000000000000000000000000
Arg [7] : 0000008000000000000000000000000000000000000000000000000000000000
Arg [8] : 000000a0000000000000000000000000203a662b0bd271a6ed5a60edfbd04bfc
Arg [9] : e608fd360000000000000000000000002dca96907fde857dd3d816880a0df407
Arg [10] : eeb2d2f200000000000000000000000000000000000000000000000000000000
Arg [11] : 0000006400000000000000000000000000000000000000000000000000000000
Arg [12] : 000002ee00000000000000000000000000000000000000000000000000000000
Arg [13] : 0000002d00000000000000000000000000000000000000000000000000000000


[ Download: CSV Export  ]
[ Download: CSV Export  ]

A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.