creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "@openzeppelin/contracts-v4/token/ERC721/IERC721.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n    /**\n     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n     */\n    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n    /**\n     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n     */\n    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n    /**\n     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n     */\n    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n    /**\n     * @dev Returns the number of tokens in ``owner``'s account.\n     */\n    function balanceOf(address owner) external view returns (uint256 balance);\n\n    /**\n     * @dev Returns the owner of the `tokenId` token.\n     *\n     * Requirements:\n     *\n     * - `tokenId` must exist.\n     */\n    function ownerOf(uint256 tokenId) external view returns (address owner);\n\n    /**\n     * @dev Safely transfers `tokenId` token from `from` to `to`.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must exist and be owned by `from`.\n     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n     *\n     * Emits a {Transfer} event.\n     */\n    function safeTransferFrom(\n        address from,\n        address to,\n        uint256 tokenId,\n        bytes calldata data\n    ) external;\n\n    /**\n     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n     * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must exist and be owned by `from`.\n     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.\n     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n     *\n     * Emits a {Transfer} event.\n     */\n    function safeTransferFrom(\n        address from,\n        address to,\n        uint256 tokenId\n    ) external;\n\n    /**\n     * @dev Transfers `tokenId` token from `from` to `to`.\n     *\n     * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n     *\n     * Requirements:\n     *\n     * - `from` cannot be the zero address.\n     * - `to` cannot be the zero address.\n     * - `tokenId` token must be owned by `from`.\n     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n     *\n     * Emits a {Transfer} event.\n     */\n    function transferFrom(\n        address from,\n        address to,\n        uint256 tokenId\n    ) external;\n\n    /**\n     * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n     * The approval is cleared when the token is transferred.\n     *\n     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n     *\n     * Requirements:\n     *\n     * - The caller must own the token or be an approved operator.\n     * - `tokenId` must exist.\n     *\n     * Emits an {Approval} event.\n     */\n    function approve(address to, uint256 tokenId) external;\n\n    /**\n     * @dev Approve or remove `operator` as an operator for the caller.\n     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n     *\n     * Requirements:\n     *\n     * - The `operator` cannot be the caller.\n     *\n     * Emits an {ApprovalForAll} event.\n     */\n    function setApprovalForAll(address operator, bool _approved) external;\n\n    /**\n     * @dev Returns the account approved for `tokenId` token.\n     *\n     * Requirements:\n     *\n     * - `tokenId` must exist.\n     */\n    function getApproved(uint256 tokenId) external view returns (address operator);\n\n    /**\n     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n     *\n     * See {setApprovalForAll}\n     */\n    function isApprovedForAll(address owner, address operator) external view returns (bool);\n}\n"
      },
      "@openzeppelin/contracts-v4/utils/introspection/IERC165.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n    /**\n     * @dev Returns true if this contract implements the interface defined by\n     * `interfaceId`. See the corresponding\n     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n     * to learn more about how these ids are created.\n     *\n     * This function call must use less than 30 000 gas.\n     */\n    function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"
      },
      "contracts/abstract/Multicall.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.18;\n\nabstract contract Multicall {\n\n  error RevertedWithoutReason(uint index);\n\n  // WARNING: Do not set this function as payable\n  function multicall(bytes[] calldata data) external returns (bytes[] memory results) {\n\n    uint callCount = data.length;\n    results = new bytes[](callCount);\n\n    for (uint i = 0; i < callCount; i++) {\n      (bool ok, bytes memory result) = address(this).delegatecall(data[i]);\n\n      if (!ok) {\n\n        uint length = result.length;\n\n        // 0 length returned from empty revert() / require(false)\n        if (length == 0) {\n          revert RevertedWithoutReason(i);\n        }\n\n        assembly {\n          revert(add(result, 0x20), length)\n        }\n      }\n\n      results[i] = result;\n    }\n  }\n}\n"
      },
      "contracts/interfaces/ICover.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\nimport \"./ICoverNFT.sol\";\nimport \"./IStakingNFT.sol\";\nimport \"./IStakingPool.sol\";\nimport \"./IStakingPoolFactory.sol\";\n\n/* ========== DATA STRUCTURES ========== */\n\nenum ClaimMethod {\n  IndividualClaims,\n  YieldTokenIncidents\n}\n\n// Basically CoverStatus from QuotationData.sol but with the extra Migrated status to avoid\n// polluting Cover.sol state layout with new status variables.\nenum LegacyCoverStatus {\n  Active,\n  ClaimAccepted,\n  ClaimDenied,\n  CoverExpired,\n  ClaimSubmitted,\n  Requested,\n  Migrated\n}\n\n/* io structs */\n\nstruct PoolAllocationRequest {\n  uint40 poolId;\n  bool skip;\n  uint coverAmountInAsset;\n}\n\nstruct RequestAllocationVariables {\n  uint previousPoolAllocationsLength;\n  uint previousPremiumInNXM;\n  uint refund;\n  uint coverAmountInNXM;\n}\n\nstruct BuyCoverParams {\n  uint coverId;\n  address owner;\n  uint24 productId;\n  uint8 coverAsset;\n  uint96 amount;\n  uint32 period;\n  uint maxPremiumInAsset;\n  uint8 paymentAsset;\n  uint16 commissionRatio;\n  address commissionDestination;\n  string ipfsData;\n}\n\nstruct ProductParam {\n  string productName;\n  uint productId;\n  string ipfsMetadata;\n  Product product;\n  uint[] allowedPools;\n}\n\nstruct ProductTypeParam {\n  string productTypeName;\n  uint productTypeId;\n  string ipfsMetadata;\n  ProductType productType;\n}\n\nstruct ProductInitializationParams {\n  uint productId;\n  uint8 weight;\n  uint96 initialPrice;\n  uint96 targetPrice;\n}\n\n/* storage structs */\n\nstruct PoolAllocation {\n  uint40 poolId;\n  uint96 coverAmountInNXM;\n  uint96 premiumInNXM;\n  uint24 allocationId;\n}\n\nstruct CoverData {\n  uint24 productId;\n  uint8 coverAsset;\n  uint96 amountPaidOut;\n}\n\nstruct CoverSegment {\n  uint96 amount;\n  uint32 start;\n  uint32 period; // seconds\n  uint32 gracePeriod; // seconds\n  uint24 globalRewardsRatio;\n  uint24 globalCapacityRatio;\n}\n\nstruct Product {\n  uint16 productType;\n  address yieldTokenAddress;\n  // cover assets bitmap. each bit represents whether the asset with\n  // the index of that bit is enabled as a cover asset for this product\n  uint32 coverAssets;\n  uint16 initialPriceRatio;\n  uint16 capacityReductionRatio;\n  bool isDeprecated;\n  bool useFixedPrice;\n}\n\nstruct ProductType {\n  uint8 claimMethod;\n  uint32 gracePeriod;\n}\n\nstruct ActiveCover {\n  // Global active cover amount per asset.\n  uint192 totalActiveCoverInAsset;\n  // The last time activeCoverExpirationBuckets was updated\n  uint64 lastBucketUpdateId;\n}\n\ninterface ICover {\n\n  /* ========== VIEWS ========== */\n\n  function coverData(uint coverId) external view returns (CoverData memory);\n\n  function coverDataCount() external view returns (uint);\n\n  function coverSegmentsCount(uint coverId) external view returns (uint);\n\n  function coverSegments(uint coverId) external view returns (CoverSegment[] memory);\n\n  function coverSegmentWithRemainingAmount(\n    uint coverId,\n    uint segmentId\n  ) external view returns (CoverSegment memory);\n\n  function products(uint id) external view returns (Product memory);\n\n  function productTypes(uint id) external view returns (ProductType memory);\n\n  function stakingPool(uint index) external view returns (IStakingPool);\n\n  function productNames(uint productId) external view returns (string memory);\n\n  function productsCount() external view returns (uint);\n\n  function productTypesCount() external view returns (uint);\n\n  function totalActiveCoverInAsset(uint coverAsset) external view returns (uint);\n\n  function globalCapacityRatio() external view returns (uint);\n\n  function globalRewardsRatio() external view returns (uint);\n\n  function getPriceAndCapacityRatios(uint[] calldata productIds) external view returns (\n    uint _globalCapacityRatio,\n    uint _globalMinPriceRatio,\n    uint[] memory _initialPriceRatios,\n    uint[] memory _capacityReductionRatios\n  );\n\n  /* === MUTATIVE FUNCTIONS ==== */\n\n  function addLegacyCover(\n    uint productId,\n    uint coverAsset,\n    uint amount,\n    uint start,\n    uint period,\n    address newOwner\n  ) external returns (uint coverId);\n\n  function buyCover(\n    BuyCoverParams calldata params,\n    PoolAllocationRequest[] calldata coverChunkRequests\n  ) external payable returns (uint coverId);\n\n  function setProductTypes(ProductTypeParam[] calldata productTypes) external;\n\n  function setProducts(ProductParam[] calldata params) external;\n\n  function burnStake(\n    uint coverId,\n    uint segmentId,\n    uint amount\n  ) external returns (address coverOwner);\n\n  function coverNFT() external returns (ICoverNFT);\n\n  function stakingNFT() external returns (IStakingNFT);\n\n  function stakingPoolFactory() external returns (IStakingPoolFactory);\n\n  function createStakingPool(\n    bool isPrivatePool,\n    uint initialPoolFee,\n    uint maxPoolFee,\n    ProductInitializationParams[] calldata productInitParams,\n    string calldata ipfsDescriptionHash\n  ) external returns (uint poolId, address stakingPoolAddress);\n\n  function isPoolAllowed(uint productId, uint poolId) external returns (bool);\n  function requirePoolIsAllowed(uint[] calldata productIds, uint poolId) external view;\n\n  /* ========== EVENTS ========== */\n\n  event ProductSet(uint id, string ipfsMetadata);\n  event ProductTypeSet(uint id, string ipfsMetadata);\n  event CoverEdited(uint indexed coverId, uint indexed productId, uint indexed segmentId, address buyer, string ipfsMetadata);\n\n  // Auth\n  error OnlyMemberRolesCanOperateTransfer();\n  error OnlyOwnerOrApproved();\n\n  // Cover details\n  error CoverPeriodTooShort();\n  error CoverPeriodTooLong();\n  error CoverOutsideOfTheGracePeriod();\n  error CoverAmountIsZero();\n\n  // Products\n  error ProductDoesntExist();\n  error ProductTypeNotFound();\n  error ProductDoesntExistOrIsDeprecated();\n  error InvalidProductType();\n  error UnexpectedProductId();\n  error PoolNotAllowedForThisProduct(uint productId);\n\n  // Cover and payment assets\n  error CoverAssetNotSupported();\n  error InvalidPaymentAsset();\n  error UnexpectedCoverAsset();\n  error UnsupportedCoverAssets();\n  error UnexpectedEthSent();\n  error EditNotSupported();\n\n  // Price & Commission\n  error PriceExceedsMaxPremiumInAsset();\n  error TargetPriceBelowGlobalMinPriceRatio();\n  error InitialPriceRatioBelowGlobalMinPriceRatio();\n  error InitialPriceRatioAbove100Percent();\n  error CommissionRateTooHigh();\n\n  // ETH transfers\n  error InsufficientEthSent();\n  error SendingEthToPoolFailed();\n  error SendingEthToCommissionDestinationFailed();\n  error ReturningEthRemainderToSenderFailed();\n\n  // Misc\n  error AlreadyInitialized();\n  error ExpiredCoversCannotBeEdited();\n  error CoverNotYetExpired(uint coverId);\n  error CoverAlreadyExpired(uint coverId);\n  error InsufficientCoverAmountAllocated();\n  error UnexpectedPoolId();\n  error CapacityReductionRatioAbove100Percent();\n}\n"
      },
      "contracts/interfaces/ICoverNFT.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\nimport \"@openzeppelin/contracts-v4/token/ERC721/IERC721.sol\";\n\ninterface ICoverNFT is IERC721 {\n\n  function isApprovedOrOwner(address spender, uint tokenId) external returns (bool);\n\n  function mint(address to) external returns (uint tokenId);\n\n  function changeOperator(address newOperator) external;\n\n  function totalSupply() external view returns (uint);\n\n  function name() external view returns (string memory);\n\n  error NotOperator();\n  error NotMinted();\n  error WrongFrom();\n  error InvalidRecipient();\n  error InvalidNewOperatorAddress();\n  error InvalidNewNFTDescriptorAddress();\n  error NotAuthorized();\n  error UnsafeRecipient();\n  error AlreadyMinted();\n\n}\n"
      },
      "contracts/interfaces/IGovernance.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\ninterface IGovernance {\n\n  event Proposal(\n    address indexed proposalOwner,\n    uint256 indexed proposalId,\n    uint256 dateAdd,\n    string proposalTitle,\n    string proposalSD,\n    string proposalDescHash\n  );\n\n  event Solution(\n    uint256 indexed proposalId,\n    address indexed solutionOwner,\n    uint256 indexed solutionId,\n    string solutionDescHash,\n    uint256 dateAdd\n  );\n\n  event Vote(\n    address indexed from,\n    uint256 indexed proposalId,\n    uint256 indexed voteId,\n    uint256 dateAdd,\n    uint256 solutionChosen\n  );\n\n  event RewardClaimed(\n    address indexed member,\n    uint gbtReward\n  );\n\n  /// @dev VoteCast event is called whenever a vote is cast that can potentially close the proposal.\n  event VoteCast (uint256 proposalId);\n\n  /// @dev ProposalAccepted event is called when a proposal is accepted so that a server can listen that can\n  ///      call any offchain actions\n  event ProposalAccepted (uint256 proposalId);\n\n  /// @dev CloseProposalOnTime event is called whenever a proposal is created or updated to close it on time.\n  event CloseProposalOnTime (\n    uint256 indexed proposalId,\n    uint256 time\n  );\n\n  /// @dev ActionSuccess event is called whenever an onchain action is executed.\n  event ActionSuccess (\n    uint256 proposalId\n  );\n\n  struct DelegateVote {\n    address follower;\n    address leader;\n    uint lastUpd;\n  }\n\n  /// @dev Creates a new proposal\n  /// @param _proposalDescHash Proposal description hash through IPFS having Short and long description of proposal\n  /// @param _categoryId This id tells under which the proposal is categorized i.e. Proposal's Objective\n  function createProposal(\n    string calldata _proposalTitle,\n    string calldata _proposalSD,\n    string calldata _proposalDescHash,\n    uint _categoryId\n  )\n  external;\n\n  /// @dev Edits the details of an existing proposal and creates new version\n  /// @param _proposalId Proposal id that details needs to be updated\n  /// @param _proposalDescHash Proposal description hash having long and short description of proposal.\n  function updateProposal(\n    uint _proposalId,\n    string calldata _proposalTitle,\n    string calldata _proposalSD,\n    string calldata _proposalDescHash\n  )\n  external;\n\n  /// @dev Categorizes proposal to proceed further. Categories shows the proposal objective.\n  function categorizeProposal(\n    uint _proposalId,\n    uint _categoryId,\n    uint _incentives\n  )\n  external;\n\n  /// @dev Submit proposal with solution\n  /// @param _proposalId Proposal id\n  /// @param _solutionHash Solution hash contains  parameters, values and description needed according to proposal\n  function submitProposalWithSolution(\n    uint _proposalId,\n    string calldata _solutionHash,\n    bytes calldata _action\n  ) external;\n\n  /// @dev Creates a new proposal with solution and votes for the solution\n  /// @param _proposalDescHash Proposal description hash through IPFS having Short and long description of proposal\n  /// @param _categoryId This id tells under which the proposal is categorized i.e. Proposal's Objective\n  /// @param _solutionHash Solution hash contains  parameters, values and description needed according to proposal\n  function createProposalwithSolution(\n    string calldata _proposalTitle,\n    string calldata _proposalSD,\n    string calldata _proposalDescHash,\n    uint _categoryId,\n    string calldata _solutionHash,\n    bytes calldata _action\n  ) external;\n\n  /// @dev Casts vote using total balance including delegated tokens via managed staking pools\n  /// @param _proposalId Proposal id\n  /// @param _solutionChosen solution chosen while voting\n  function submitVote(uint _proposalId, uint _solutionChosen) external;\n\n  /// @dev Casts vote using total balance without delegated tokens via managed staking pools\n  /// @param _proposalId Proposal id\n  /// @param _solutionChosen solution chosen while voting\n  function submitVoteWithoutDelegations(uint _proposalId, uint _solutionChosen) external;\n\n  function closeProposal(uint _proposalId) external;\n\n  function tokenHoldingTime() external returns (uint);\n\n  function claimReward(address _memberAddress, uint _maxRecords) external returns (uint pendingDAppReward);\n\n  function proposal(uint _proposalId) external view returns (\n    uint proposalId,\n    uint category,\n    uint status,\n    uint finalVerdict,\n    uint totalReward\n  );\n\n  function canCloseProposal(uint _proposalId) external view returns (uint closeValue);\n\n  function allowedToCatgorize() external view returns (uint roleId);\n\n  function getPendingReward(address _memberAddress) external view returns (uint pendingDAppReward);\n\n  function getFollowers(address _add) external view returns (uint[] memory);\n\n  function followerDelegation(address _add) external view returns (uint delegationId);\n\n  function allDelegation(uint _delegationId) external view returns (address follower, address leader, uint lastUpd);\n\n}\n"
      },
      "contracts/interfaces/INXMMaster.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\ninterface INXMMaster {\n\n  function tokenAddress() external view returns (address);\n\n  function owner() external view returns (address);\n\n  function emergencyAdmin() external view returns (address);\n\n  function masterInitialized() external view returns (bool);\n\n  function isInternal(address _add) external view returns (bool);\n\n  function isPause() external view returns (bool check);\n\n  function isMember(address _add) external view returns (bool);\n\n  function checkIsAuthToGoverned(address _add) external view returns (bool);\n\n  function getLatestAddress(bytes2 _contractName) external view returns (address payable contractAddress);\n\n  function contractAddresses(bytes2 code) external view returns (address payable);\n\n  function upgradeMultipleContracts(\n    bytes2[] calldata _contractCodes,\n    address payable[] calldata newAddresses\n  ) external;\n\n  function removeContracts(bytes2[] calldata contractCodesToRemove) external;\n\n  function addNewInternalContracts(\n    bytes2[] calldata _contractCodes,\n    address payable[] calldata newAddresses,\n    uint[] calldata _types\n  ) external;\n\n  function updateOwnerParameters(bytes8 code, address payable val) external;\n}\n"
      },
      "contracts/interfaces/INXMToken.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\ninterface INXMToken {\n\n  function burn(uint256 amount) external returns (bool);\n\n  function burnFrom(address from, uint256 value) external returns (bool);\n\n  function operatorTransfer(address from, uint256 value) external returns (bool);\n\n  function mint(address account, uint256 amount) external;\n\n  function isLockedForMV(address member) external view returns (uint);\n\n  function whiteListed(address member) external view returns (bool);\n\n  function addToWhiteList(address _member) external returns (bool);\n\n  function removeFromWhiteList(address _member) external returns (bool);\n\n  function changeOperator(address _newOperator) external returns (bool);\n\n  function lockForMemberVote(address _of, uint _days) external;\n\n  /**\n * @dev Returns the amount of tokens in existence.\n */\n  function totalSupply() external view returns (uint256);\n\n  /**\n   * @dev Returns the amount of tokens owned by `account`.\n   */\n  function balanceOf(address account) external view returns (uint256);\n\n  /**\n   * @dev Moves `amount` tokens from the caller's account to `recipient`.\n   *\n   * Returns a boolean value indicating whether the operation succeeded.\n   *\n   * Emits a {Transfer} event.\n   */\n  function transfer(address recipient, uint256 amount) external returns (bool);\n\n  /**\n   * @dev Returns the remaining number of tokens that `spender` will be\n   * allowed to spend on behalf of `owner` through {transferFrom}. This is\n   * zero by default.\n   *\n   * This value changes when {approve} or {transferFrom} are called.\n   */\n  function allowance(address owner, address spender) external view returns (uint256);\n\n  /**\n   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n   *\n   * Returns a boolean value indicating whether the operation succeeded.\n   *\n   * IMPORTANT: Beware that changing an allowance with this method brings the risk\n   * that someone may use both the old and the new allowance by unfortunate\n   * transaction ordering. One possible solution to mitigate this race\n   * condition is to first reduce the spender's allowance to 0 and set the\n   * desired value afterwards:\n   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n   *\n   * Emits an {Approval} event.\n   */\n  function approve(address spender, uint256 amount) external returns (bool);\n\n  /**\n   * @dev Moves `amount` tokens from `sender` to `recipient` using the\n   * allowance mechanism. `amount` is then deducted from the caller's\n   * allowance.\n   *\n   * Returns a boolean value indicating whether the operation succeeded.\n   *\n   * Emits a {Transfer} event.\n   */\n  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\n\n  /**\n   * @dev Emitted when `value` tokens are moved from one account (`from`) to\n   * another (`to`).\n   *\n   * Note that `value` may be zero.\n   */\n  event Transfer(address indexed from, address indexed to, uint256 value);\n\n  /**\n   * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n   * a call to {approve}. `value` is the new allowance.\n   */\n  event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n"
      },
      "contracts/interfaces/IStakingNFT.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\nimport \"@openzeppelin/contracts-v4/token/ERC721/IERC721.sol\";\n\ninterface IStakingNFT is IERC721 {\n\n  function isApprovedOrOwner(address spender, uint tokenId) external returns (bool);\n\n  function mint(uint poolId, address to) external returns (uint tokenId);\n\n  function changeOperator(address newOperator) external;\n\n  function totalSupply() external returns (uint);\n\n  function tokenInfo(uint tokenId) external view returns (uint poolId, address owner);\n\n  function stakingPoolOf(uint tokenId) external view returns (uint poolId);\n\n  function stakingPoolFactory() external view returns (address);\n\n  function name() external view returns (string memory);\n\n  error NotOperator();\n  error NotMinted();\n  error WrongFrom();\n  error InvalidRecipient();\n  error InvalidNewOperatorAddress();\n  error InvalidNewNFTDescriptorAddress();\n  error NotAuthorized();\n  error UnsafeRecipient();\n  error AlreadyMinted();\n  error NotStakingPool();\n\n}\n"
      },
      "contracts/interfaces/IStakingPool.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\n/* structs for io */\n\nstruct AllocationRequest {\n  uint productId;\n  uint coverId;\n  uint allocationId;\n  uint period;\n  uint gracePeriod;\n  bool useFixedPrice;\n  uint previousStart;\n  uint previousExpiration;\n  uint previousRewardsRatio;\n  uint globalCapacityRatio;\n  uint capacityReductionRatio;\n  uint rewardRatio;\n  uint globalMinPrice;\n}\n\nstruct StakedProductParam {\n  uint productId;\n  bool recalculateEffectiveWeight;\n  bool setTargetWeight;\n  uint8 targetWeight;\n  bool setTargetPrice;\n  uint96 targetPrice;\n}\n\n  struct BurnStakeParams {\n    uint allocationId;\n    uint productId;\n    uint start;\n    uint period;\n    uint deallocationAmount;\n  }\n\ninterface IStakingPool {\n\n  /* structs for storage */\n\n  // stakers are grouped in tranches based on the timelock expiration\n  // tranche index is calculated based on the expiration date\n  // the initial proposal is to have 4 tranches per year (1 tranche per quarter)\n  struct Tranche {\n    uint128 stakeShares;\n    uint128 rewardsShares;\n  }\n\n  struct ExpiredTranche {\n    uint96 accNxmPerRewardShareAtExpiry;\n    uint96 stakeAmountAtExpiry; // nxm total supply is 6.7e24 and uint96.max is 7.9e28\n    uint128 stakeSharesSupplyAtExpiry;\n  }\n\n  struct Deposit {\n    uint96 lastAccNxmPerRewardShare;\n    uint96 pendingRewards;\n    uint128 stakeShares;\n    uint128 rewardsShares;\n  }\n\n  function initialize(\n    bool isPrivatePool,\n    uint initialPoolFee,\n    uint maxPoolFee,\n    uint _poolId,\n    string memory ipfsDescriptionHash\n  ) external;\n\n  function processExpirations(bool updateUntilCurrentTimestamp) external;\n\n  function requestAllocation(\n    uint amount,\n    uint previousPremium,\n    AllocationRequest calldata request\n  ) external returns (uint premium, uint allocationId);\n\n  function burnStake(uint amount, BurnStakeParams calldata params) external;\n\n  function depositTo(\n    uint amount,\n    uint trancheId,\n    uint requestTokenId,\n    address destination\n  ) external returns (uint tokenId);\n\n  function withdraw(\n    uint tokenId,\n    bool withdrawStake,\n    bool withdrawRewards,\n    uint[] memory trancheIds\n  ) external returns (uint withdrawnStake, uint withdrawnRewards);\n\n  function isPrivatePool() external view returns (bool);\n\n  function isHalted() external view returns (bool);\n\n  function manager() external view returns (address);\n\n  function getPoolId() external view returns (uint);\n\n  function getPoolFee() external view returns (uint);\n\n  function getMaxPoolFee() external view returns (uint);\n\n  function getActiveStake() external view returns (uint);\n\n  function getStakeSharesSupply() external view returns (uint);\n\n  function getRewardsSharesSupply() external view returns (uint);\n\n  function getRewardPerSecond() external view returns (uint);\n\n  function getAccNxmPerRewardsShare() external view returns (uint);\n\n  function getLastAccNxmUpdate() external view returns (uint);\n\n  function getFirstActiveTrancheId() external view returns (uint);\n\n  function getFirstActiveBucketId() external view returns (uint);\n\n  function getNextAllocationId() external view returns (uint);\n\n  function getDeposit(uint tokenId, uint trancheId) external view returns (\n    uint lastAccNxmPerRewardShare,\n    uint pendingRewards,\n    uint stakeShares,\n    uint rewardsShares\n  );\n\n  function getTranche(uint trancheId) external view returns (\n    uint stakeShares,\n    uint rewardsShares\n  );\n\n  function getExpiredTranche(uint trancheId) external view returns (\n    uint accNxmPerRewardShareAtExpiry,\n    uint stakeAmountAtExpiry,\n    uint stakeShareSupplyAtExpiry\n  );\n\n  function setPoolFee(uint newFee) external;\n\n  function setPoolPrivacy(bool isPrivatePool) external;\n\n  function getActiveAllocations(\n    uint productId\n  ) external view returns (uint[] memory trancheAllocations);\n\n  function getTrancheCapacities(\n    uint productId,\n    uint firstTrancheId,\n    uint trancheCount,\n    uint capacityRatio,\n    uint reductionRatio\n  ) external view returns (uint[] memory trancheCapacities);\n\n  /* ========== EVENTS ========== */\n\n  event StakeDeposited(address indexed user, uint256 amount, uint256 trancheId, uint256 tokenId);\n\n  event DepositExtended(address indexed user, uint256 tokenId, uint256 initialTrancheId, uint256 newTrancheId, uint256 topUpAmount);\n\n  event PoolPrivacyChanged(address indexed manager, bool isPrivate);\n\n  event PoolFeeChanged(address indexed manager, uint newFee);\n\n  event PoolDescriptionSet(string ipfsDescriptionHash);\n\n  event Withdraw(address indexed user, uint indexed tokenId, uint tranche, uint amountStakeWithdrawn, uint amountRewardsWithdrawn);\n\n  event StakeBurned(uint amount);\n\n  event Deallocated(uint productId);\n\n  event BucketExpired(uint bucketId);\n\n  event TrancheExpired(uint trancheId);\n\n  // Auth\n  error OnlyCoverContract();\n  error OnlyManager();\n  error PrivatePool();\n  error SystemPaused();\n  error PoolHalted();\n\n  // Fees\n  error PoolFeeExceedsMax();\n  error MaxPoolFeeAbove100();\n\n  // Voting\n  error NxmIsLockedForGovernanceVote();\n  error ManagerNxmIsLockedForGovernanceVote();\n\n  // Deposit\n  error InsufficientDepositAmount();\n  error RewardRatioTooHigh();\n\n  // Staking NFTs\n  error InvalidTokenId();\n  error NotTokenOwnerOrApproved();\n  error InvalidStakingPoolForToken();\n\n  // Tranche & capacity\n  error NewTrancheEndsBeforeInitialTranche();\n  error RequestedTrancheIsNotYetActive();\n  error RequestedTrancheIsExpired();\n  error InsufficientCapacity();\n\n  // Allocation\n  error AlreadyDeallocated(uint allocationId);\n}\n"
      },
      "contracts/interfaces/IStakingPoolFactory.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\ninterface IStakingPoolFactory {\n\n  function stakingPoolCount() external view returns (uint);\n\n  function beacon() external view returns (address);\n\n  function create(address beacon) external returns (uint poolId, address stakingPoolAddress);\n\n  event StakingPoolCreated(uint indexed poolId, address indexed stakingPoolAddress);\n}\n"
      },
      "contracts/interfaces/IStakingProducts.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\nimport \"./ICover.sol\";\nimport \"./IStakingPool.sol\";\n\ninterface IStakingProducts {\n\n  // TODO: resize values?\n  struct Weights {\n    uint32 totalEffectiveWeight;\n    uint32 totalTargetWeight;\n  }\n\n  struct StakedProduct {\n    uint16 lastEffectiveWeight;\n    uint8 targetWeight;\n    uint96 targetPrice;\n    uint96 bumpedPrice;\n    uint32 bumpedPriceUpdateTime;\n  }\n\n  /* ============= PRODUCT FUNCTIONS ============= */\n\n  function setProducts(uint poolId, StakedProductParam[] memory params) external;\n\n  function setInitialProducts(uint poolId, ProductInitializationParams[] memory params) external;\n\n  function getProductTargetWeight(uint poolId, uint productId) external view returns (uint);\n\n  function getTotalTargetWeight(uint poolId) external view returns (uint);\n\n  function getTotalEffectiveWeight(uint poolId) external view returns (uint);\n\n  function getProduct(uint poolId, uint productId) external view returns (\n    uint lastEffectiveWeight,\n    uint targetWeight,\n    uint targetPrice,\n    uint bumpedPrice,\n    uint bumpedPriceUpdateTime\n  );\n\n  /* ============= PRICING FUNCTIONS ============= */\n\n  function getPremium(\n    uint poolId,\n    uint productId,\n    uint period,\n    uint coverAmount,\n    uint initialCapacityUsed,\n    uint totalCapacity,\n    uint globalMinPrice,\n    bool useFixedPrice,\n    uint nxmPerAllocationUnit,\n    uint allocationUnitsPerNxm\n  ) external returns (uint premium);\n\n  function calculateFixedPricePremium(\n    uint coverAmount,\n    uint period,\n    uint fixedPrice,\n    uint nxmPerAllocationUnit,\n    uint targetPriceDenominator\n  ) external pure returns (uint);\n\n\n  function calculatePremium(\n    StakedProduct memory product,\n    uint period,\n    uint coverAmount,\n    uint initialCapacityUsed,\n    uint totalCapacity,\n    uint targetPrice,\n    uint currentBlockTimestamp,\n    uint nxmPerAllocationUnit,\n    uint allocationUnitsPerNxm,\n    uint targetPriceDenominator\n  ) external pure returns (uint premium, StakedProduct memory);\n\n  function calculatePremiumPerYear(\n    uint basePrice,\n    uint coverAmount,\n    uint initialCapacityUsed,\n    uint totalCapacity,\n    uint nxmPerAllocationUnit,\n    uint allocationUnitsPerNxm,\n    uint targetPriceDenominator\n  ) external pure returns (uint);\n\n  // Calculates the premium for a given cover amount starting with the surge point\n  function calculateSurgePremium(\n    uint amountOnSurge,\n    uint totalCapacity,\n    uint allocationUnitsPerNxm\n  ) external pure returns (uint);\n\n  /* ============= EVENTS ============= */\n\n  event ProductUpdated(uint productId, uint8 targetWeight, uint96 targetPrice);\n\n  /* ============= ERRORS ============= */\n\n  // Auth\n  error OnlyStakingPool();\n  error OnlyCoverContract();\n  error OnlyManager();\n\n  // Products & weights\n  error MustSetPriceForNewProducts();\n  error MustSetWeightForNewProducts();\n  error TargetPriceTooHigh();\n  error TargetPriceBelowMin();\n  error TargetWeightTooHigh();\n  error MustRecalculateEffectiveWeight();\n  error TotalTargetWeightExceeded();\n  error TotalEffectiveWeightExceeded();\n\n}\n"
      },
      "contracts/interfaces/ITokenController.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.5.0;\n\nimport \"./INXMToken.sol\";\n\ninterface ITokenController {\n\n  struct StakingPoolNXMBalances {\n    uint128 rewards;\n    uint128 deposits;\n  }\n\n  struct CoverInfo {\n    uint16 claimCount;\n    bool hasOpenClaim;\n    bool hasAcceptedClaim;\n    uint96 requestedPayoutAmount;\n    // note: still 128 bits available here, can be used later\n  }\n\n  struct StakingPoolOwnershipOffer {\n    address proposedManager;\n    uint96 deadline;\n  }\n\n  function coverInfo(uint id) external view returns (\n    uint16 claimCount,\n    bool hasOpenClaim,\n    bool hasAcceptedClaim,\n    uint96 requestedPayoutAmount\n  );\n\n  function withdrawCoverNote(\n    address _of,\n    uint[] calldata _coverIds,\n    uint[] calldata _indexes\n  ) external;\n\n  function changeOperator(address _newOperator) external;\n\n  function operatorTransfer(address _from, address _to, uint _value) external returns (bool);\n\n  function burnFrom(address _of, uint amount) external returns (bool);\n\n  function addToWhitelist(address _member) external;\n\n  function removeFromWhitelist(address _member) external;\n\n  function mint(address _member, uint _amount) external;\n\n  function lockForMemberVote(address _of, uint _days) external;\n\n  function withdrawClaimAssessmentTokens(address[] calldata users) external;\n\n  function getLockReasons(address _of) external view returns (bytes32[] memory reasons);\n\n  function totalSupply() external view returns (uint);\n\n  function totalBalanceOf(address _of) external view returns (uint amount);\n\n  function totalBalanceOfWithoutDelegations(address _of) external view returns (uint amount);\n\n  function getTokenPrice() external view returns (uint tokenPrice);\n\n  function token() external view returns (INXMToken);\n\n  function getStakingPoolManager(uint poolId) external view returns (address manager);\n\n  function getManagerStakingPools(address manager) external view returns (uint[] memory poolIds);\n\n  function isStakingPoolManager(address member) external view returns (bool);\n\n  function getStakingPoolOwnershipOffer(uint poolId) external view returns (address proposedManager, uint deadline);\n\n  function transferStakingPoolsOwnership(address from, address to) external;\n\n  function assignStakingPoolManager(uint poolId, address manager) external;\n\n  function createStakingPoolOwnershipOffer(uint poolId, address proposedManager, uint deadline) external;\n\n  function acceptStakingPoolOwnershipOffer(uint poolId) external;\n\n  function cancelStakingPoolOwnershipOffer(uint poolId) external;\n\n  function mintStakingPoolNXMRewards(uint amount, uint poolId) external;\n\n  function burnStakingPoolNXMRewards(uint amount, uint poolId) external;\n\n  function depositStakedNXM(address from, uint amount, uint poolId) external;\n\n  function withdrawNXMStakeAndRewards(address to, uint stakeToWithdraw, uint rewardsToWithdraw, uint poolId) external;\n\n  function burnStakedNXM(uint amount, uint poolId) external;\n\n  function stakingPoolNXMBalances(uint poolId) external view returns(uint128 rewards, uint128 deposits);\n\n  function tokensLocked(address _of, bytes32 _reason) external view returns (uint256 amount);\n\n  function getWithdrawableCoverNotes(\n    address coverOwner\n  ) external view returns (\n    uint[] memory coverIds,\n    bytes32[] memory lockReasons,\n    uint withdrawableAmount\n  );\n\n  function getPendingRewards(address member) external view returns (uint);\n}\n"
      },
      "contracts/libraries/Math.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.18;\n\n/**\n * @dev Simple library that defines min, max and babylonian sqrt functions\n */\nlibrary Math {\n\n  function min(uint a, uint b) internal pure returns (uint) {\n    return a < b ? a : b;\n  }\n\n  function max(uint a, uint b) internal pure returns (uint) {\n    return a > b ? a : b;\n  }\n\n  function sum(uint[] memory items) internal pure returns (uint) {\n    uint count = items.length;\n    uint total;\n\n    for (uint i = 0; i < count; i++) {\n      total += items[i];\n    }\n\n    return total;\n  }\n\n  function divRound(uint a, uint b) internal pure returns (uint) {\n    return (a + b / 2) / b;\n  }\n\n  function divCeil(uint a, uint b) internal pure returns (uint) {\n    return (a + b - 1) / b;\n  }\n\n  function roundUp(uint a, uint b) internal pure returns (uint) {\n    return divCeil(a, b) * b;\n  }\n\n  // babylonian method\n  function sqrt(uint y) internal pure returns (uint) {\n\n    if (y > 3) {\n      uint z = y;\n      uint x = y / 2 + 1;\n      while (x < z) {\n        z = x;\n        x = (y / x + x) / 2;\n      }\n      return z;\n    }\n\n    if (y != 0) {\n      return 1;\n    }\n\n    return 0;\n  }\n\n}\n"
      },
      "contracts/libraries/SafeUintCast.sol": {
        "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.18;\n\n/**\n * @dev Wrappers over Solidity's uintXX casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeUintCast {\n  /**\n   * @dev Returns the downcasted uint248 from uint256, reverting on\n   * overflow (when the input is greater than largest uint248).\n   *\n   * Counterpart to Solidity's `uint248` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 248 bits\n   */\n  function toUint248(uint256 value) internal pure returns (uint248) {\n    require(value < 2**248, \"SafeCast: value doesn\\'t fit in 248 bits\");\n    return uint248(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint240 from uint256, reverting on\n   * overflow (when the input is greater than largest uint240).\n   *\n   * Counterpart to Solidity's `uint240` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 240 bits\n   */\n  function toUint240(uint256 value) internal pure returns (uint240) {\n    require(value < 2**240, \"SafeCast: value doesn\\'t fit in 240 bits\");\n    return uint240(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint232 from uint256, reverting on\n   * overflow (when the input is greater than largest uint232).\n   *\n   * Counterpart to Solidity's `uint232` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 232 bits\n   */\n  function toUint232(uint256 value) internal pure returns (uint232) {\n    require(value < 2**232, \"SafeCast: value doesn\\'t fit in 232 bits\");\n    return uint232(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint224 from uint256, reverting on\n   * overflow (when the input is greater than largest uint224).\n   *\n   * Counterpart to Solidity's `uint224` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 224 bits\n   */\n  function toUint224(uint256 value) internal pure returns (uint224) {\n    require(value < 2**224, \"SafeCast: value doesn\\'t fit in 224 bits\");\n    return uint224(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint216 from uint256, reverting on\n   * overflow (when the input is greater than largest uint216).\n   *\n   * Counterpart to Solidity's `uint216` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 216 bits\n   */\n  function toUint216(uint256 value) internal pure returns (uint216) {\n    require(value < 2**216, \"SafeCast: value doesn\\'t fit in 216 bits\");\n    return uint216(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint208 from uint256, reverting on\n   * overflow (when the input is greater than largest uint208).\n   *\n   * Counterpart to Solidity's `uint208` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 208 bits\n   */\n  function toUint208(uint256 value) internal pure returns (uint208) {\n    require(value < 2**208, \"SafeCast: value doesn\\'t fit in 208 bits\");\n    return uint208(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint200 from uint256, reverting on\n   * overflow (when the input is greater than largest uint200).\n   *\n   * Counterpart to Solidity's `uint200` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 200 bits\n   */\n  function toUint200(uint256 value) internal pure returns (uint200) {\n    require(value < 2**200, \"SafeCast: value doesn\\'t fit in 200 bits\");\n    return uint200(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint192 from uint256, reverting on\n   * overflow (when the input is greater than largest uint192).\n   *\n   * Counterpart to Solidity's `uint192` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 192 bits\n   */\n  function toUint192(uint256 value) internal pure returns (uint192) {\n    require(value < 2**192, \"SafeCast: value doesn\\'t fit in 192 bits\");\n    return uint192(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint184 from uint256, reverting on\n   * overflow (when the input is greater than largest uint184).\n   *\n   * Counterpart to Solidity's `uint184` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 184 bits\n   */\n  function toUint184(uint256 value) internal pure returns (uint184) {\n    require(value < 2**184, \"SafeCast: value doesn\\'t fit in 184 bits\");\n    return uint184(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint176 from uint256, reverting on\n   * overflow (when the input is greater than largest uint176).\n   *\n   * Counterpart to Solidity's `uint176` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 176 bits\n   */\n  function toUint176(uint256 value) internal pure returns (uint176) {\n    require(value < 2**176, \"SafeCast: value doesn\\'t fit in 176 bits\");\n    return uint176(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint168 from uint256, reverting on\n   * overflow (when the input is greater than largest uint168).\n   *\n   * Counterpart to Solidity's `uint168` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 168 bits\n   */\n  function toUint168(uint256 value) internal pure returns (uint168) {\n    require(value < 2**168, \"SafeCast: value doesn\\'t fit in 168 bits\");\n    return uint168(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint160 from uint256, reverting on\n   * overflow (when the input is greater than largest uint160).\n   *\n   * Counterpart to Solidity's `uint160` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 160 bits\n   */\n  function toUint160(uint256 value) internal pure returns (uint160) {\n    require(value < 2**160, \"SafeCast: value doesn\\'t fit in 160 bits\");\n    return uint160(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint152 from uint256, reverting on\n   * overflow (when the input is greater than largest uint152).\n   *\n   * Counterpart to Solidity's `uint152` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 152 bits\n   */\n  function toUint152(uint256 value) internal pure returns (uint152) {\n    require(value < 2**152, \"SafeCast: value doesn\\'t fit in 152 bits\");\n    return uint152(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint144 from uint256, reverting on\n   * overflow (when the input is greater than largest uint144).\n   *\n   * Counterpart to Solidity's `uint144` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 144 bits\n   */\n  function toUint144(uint256 value) internal pure returns (uint144) {\n    require(value < 2**144, \"SafeCast: value doesn\\'t fit in 144 bits\");\n    return uint144(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint136 from uint256, reverting on\n   * overflow (when the input is greater than largest uint136).\n   *\n   * Counterpart to Solidity's `uint136` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 136 bits\n   */\n  function toUint136(uint256 value) internal pure returns (uint136) {\n    require(value < 2**136, \"SafeCast: value doesn\\'t fit in 136 bits\");\n    return uint136(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint128 from uint256, reverting on\n   * overflow (when the input is greater than largest uint128).\n   *\n   * Counterpart to Solidity's `uint128` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 128 bits\n   */\n  function toUint128(uint256 value) internal pure returns (uint128) {\n    require(value < 2**128, \"SafeCast: value doesn\\'t fit in 128 bits\");\n    return uint128(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint120 from uint256, reverting on\n   * overflow (when the input is greater than largest uint120).\n   *\n   * Counterpart to Solidity's `uint120` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 120 bits\n   */\n  function toUint120(uint256 value) internal pure returns (uint120) {\n    require(value < 2**120, \"SafeCast: value doesn\\'t fit in 120 bits\");\n    return uint120(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint112 from uint256, reverting on\n   * overflow (when the input is greater than largest uint112).\n   *\n   * Counterpart to Solidity's `uint112` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 112 bits\n   */\n  function toUint112(uint256 value) internal pure returns (uint112) {\n    require(value < 2**112, \"SafeCast: value doesn\\'t fit in 112 bits\");\n    return uint112(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint104 from uint256, reverting on\n   * overflow (when the input is greater than largest uint104).\n   *\n   * Counterpart to Solidity's `uint104` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 104 bits\n   */\n  function toUint104(uint256 value) internal pure returns (uint104) {\n    require(value < 2**104, \"SafeCast: value doesn\\'t fit in 104 bits\");\n    return uint104(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint96 from uint256, reverting on\n   * overflow (when the input is greater than largest uint96).\n   *\n   * Counterpart to Solidity's `uint104` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 96 bits\n   */\n  function toUint96(uint256 value) internal pure returns (uint96) {\n    require(value < 2**96, \"SafeCast: value doesn\\'t fit in 96 bits\");\n    return uint96(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint88 from uint256, reverting on\n   * overflow (when the input is greater than largest uint88).\n   *\n   * Counterpart to Solidity's `uint104` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 88 bits\n   */\n  function toUint88(uint256 value) internal pure returns (uint88) {\n    require(value < 2**88, \"SafeCast: value doesn\\'t fit in 88 bits\");\n    return uint88(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint80 from uint256, reverting on\n   * overflow (when the input is greater than largest uint80).\n   *\n   * Counterpart to Solidity's `uint104` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 80 bits\n   */\n  function toUint80(uint256 value) internal pure returns (uint80) {\n    require(value < 2**80, \"SafeCast: value doesn\\'t fit in 80 bits\");\n    return uint80(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint64 from uint256, reverting on\n   * overflow (when the input is greater than largest uint64).\n   *\n   * Counterpart to Solidity's `uint64` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 64 bits\n   */\n  function toUint64(uint256 value) internal pure returns (uint64) {\n    require(value < 2**64, \"SafeCast: value doesn\\'t fit in 64 bits\");\n    return uint64(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint56 from uint256, reverting on\n   * overflow (when the input is greater than largest uint56).\n   *\n   * Counterpart to Solidity's `uint56` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 56 bits\n   */\n  function toUint56(uint256 value) internal pure returns (uint56) {\n    require(value < 2**56, \"SafeCast: value doesn\\'t fit in 56 bits\");\n    return uint56(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint48 from uint256, reverting on\n   * overflow (when the input is greater than largest uint48).\n   *\n   * Counterpart to Solidity's `uint48` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 48 bits\n   */\n  function toUint48(uint256 value) internal pure returns (uint48) {\n    require(value < 2**48, \"SafeCast: value doesn\\'t fit in 48 bits\");\n    return uint48(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint40 from uint256, reverting on\n   * overflow (when the input is greater than largest uint40).\n   *\n   * Counterpart to Solidity's `uint40` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 40 bits\n   */\n  function toUint40(uint256 value) internal pure returns (uint40) {\n    require(value < 2**40, \"SafeCast: value doesn\\'t fit in 40 bits\");\n    return uint40(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint32 from uint256, reverting on\n   * overflow (when the input is greater than largest uint32).\n   *\n   * Counterpart to Solidity's `uint32` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 32 bits\n   */\n  function toUint32(uint256 value) internal pure returns (uint32) {\n    require(value < 2**32, \"SafeCast: value doesn\\'t fit in 32 bits\");\n    return uint32(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint24 from uint256, reverting on\n   * overflow (when the input is greater than largest uint24).\n   *\n   * Counterpart to Solidity's `uint24` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 24 bits\n   */\n  function toUint24(uint256 value) internal pure returns (uint24) {\n    require(value < 2**24, \"SafeCast: value doesn\\'t fit in 24 bits\");\n    return uint24(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint16 from uint256, reverting on\n   * overflow (when the input is greater than largest uint16).\n   *\n   * Counterpart to Solidity's `uint16` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 16 bits\n   */\n  function toUint16(uint256 value) internal pure returns (uint16) {\n    require(value < 2**16, \"SafeCast: value doesn\\'t fit in 16 bits\");\n    return uint16(value);\n  }\n\n  /**\n   * @dev Returns the downcasted uint8 from uint256, reverting on\n   * overflow (when the input is greater than largest uint8).\n   *\n   * Counterpart to Solidity's `uint8` operator.\n   *\n   * Requirements:\n   *\n   * - input must fit into 8 bits.\n   */\n  function toUint8(uint256 value) internal pure returns (uint8) {\n    require(value < 2**8, \"SafeCast: value doesn\\'t fit in 8 bits\");\n    return uint8(value);\n  }\n}\n"
      },
      "contracts/libraries/UncheckedMath.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.18;\n\n/**\n * @dev Simple library that defines basic math functions that allow overflow\n */\nlibrary UncheckedMath {\n\n  function uncheckedAdd(uint a, uint b) internal pure returns (uint) {\n    unchecked { return a + b; }\n  }\n\n  function uncheckedSub(uint a, uint b) internal pure returns (uint) {\n    unchecked { return a - b; }\n  }\n\n  function uncheckedMul(uint a, uint b) internal pure returns (uint) {\n    unchecked { return a * b; }\n  }\n\n  function uncheckedDiv(uint a, uint b) internal pure returns (uint) {\n    unchecked { return a / b; }\n  }\n\n}\n"
      },
      "contracts/modules/staking/StakingPool.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.18;\n\nimport \"../../abstract/Multicall.sol\";\nimport \"../../interfaces/IStakingPool.sol\";\nimport \"../../interfaces/IGovernance.sol\";\nimport \"../../interfaces/ICover.sol\";\nimport \"../../interfaces/IStakingNFT.sol\";\nimport \"../../interfaces/ITokenController.sol\";\nimport \"../../interfaces/INXMMaster.sol\";\nimport \"../../interfaces/INXMToken.sol\";\nimport \"../../interfaces/IStakingProducts.sol\";\nimport \"../../libraries/Math.sol\";\nimport \"../../libraries/UncheckedMath.sol\";\nimport \"../../libraries/SafeUintCast.sol\";\nimport \"./StakingTypesLib.sol\";\n\n// total stake = active stake + expired stake\n// total capacity = active stake * global capacity factor\n// total product capacity = total capacity * capacity reduction factor * product weight\n// total product capacity = allocated product capacity + available product capacity\n// on cover buys we allocate the available product capacity\n// on cover expiration we deallocate the capacity and it becomes available again\n\ncontract StakingPool is IStakingPool, Multicall {\n  using StakingTypesLib for TrancheAllocationGroup;\n  using StakingTypesLib for TrancheGroupBucket;\n  using SafeUintCast for uint;\n  using UncheckedMath for uint;\n\n  /* storage */\n\n  // slot 1\n  // supply of pool stake shares used by tranches\n  uint128 internal stakeSharesSupply;\n\n  // supply of pool rewards shares used by tranches\n  uint128 internal rewardsSharesSupply;\n\n  // slot 2\n  // accumulated rewarded nxm per reward share\n  uint96 internal accNxmPerRewardsShare;\n\n  // currently active staked nxm amount\n  uint96 internal activeStake;\n\n  uint32 internal firstActiveTrancheId;\n  uint32 internal firstActiveBucketId;\n\n  // slot 3\n  // timestamp when accNxmPerRewardsShare was last updated\n  uint32 internal lastAccNxmUpdate;\n  // current nxm reward per second for the entire pool\n  // applies to active stake only and does not need update on deposits\n  uint96 internal rewardPerSecond;\n\n  uint40 internal poolId;\n  uint24 internal lastAllocationId;\n\n  bool public override isPrivatePool;\n  bool public override isHalted;\n\n  uint8 internal poolFee;\n  uint8 internal maxPoolFee;\n\n  // 32 bytes left in slot 3\n\n  // tranche id => tranche data\n  mapping(uint => Tranche) internal tranches;\n\n  // tranche id => expired tranche data\n  mapping(uint => ExpiredTranche) internal expiredTranches;\n\n  // reward bucket id => RewardBucket\n  mapping(uint => uint) public rewardPerSecondCut;\n\n  // product id => tranche group id => active allocations for a tranche group\n  mapping(uint => mapping(uint => TrancheAllocationGroup)) public trancheAllocationGroups;\n\n  // product id => bucket id => bucket tranche group id => tranche group's expiring cover amounts\n  mapping(uint => mapping(uint => mapping(uint => TrancheGroupBucket))) public expiringCoverBuckets;\n\n  // cover id => per tranche cover amounts (8 32-bit values, one per tranche, packed in a slot)\n  // starts with the first active tranche at the time of cover buy\n  mapping(uint => uint) public coverTrancheAllocations;\n\n  // token id => tranche id => deposit data\n  mapping(uint => mapping(uint => Deposit)) public deposits;\n\n  /* immutables */\n\n  IStakingNFT public immutable stakingNFT;\n  INXMToken public immutable nxm;\n  ITokenController public  immutable tokenController;\n  address public immutable coverContract;\n  INXMMaster public immutable masterContract;\n  IStakingProducts public immutable stakingProducts;\n\n  /* constants */\n\n  // 7 * 13 = 91\n  uint public constant BUCKET_DURATION = 28 days;\n  uint public constant TRANCHE_DURATION = 91 days;\n  uint public constant MAX_ACTIVE_TRANCHES = 8; // 7 whole quarters + 1 partial quarter\n\n  uint public constant COVER_TRANCHE_GROUP_SIZE = 5;\n  uint public constant BUCKET_TRANCHE_GROUP_SIZE = 8;\n\n  uint public constant REWARD_BONUS_PER_TRANCHE_RATIO = 10_00; // 10.00%\n  uint public constant REWARD_BONUS_PER_TRANCHE_DENOMINATOR = 100_00;\n  uint public constant WEIGHT_DENOMINATOR = 100;\n  uint public constant REWARDS_DENOMINATOR = 100_00;\n  uint public constant POOL_FEE_DENOMINATOR = 100;\n\n  // denominators for cover contract parameters\n  uint public constant GLOBAL_CAPACITY_DENOMINATOR = 100_00;\n  uint public constant CAPACITY_REDUCTION_DENOMINATOR = 100_00;\n\n  // +2% for every 1%, ie +200% for 100%\n\n  // 1 nxm = 1e18\n  uint public constant ONE_NXM = 1 ether;\n\n  // internally we store capacity using 2 decimals\n  // 1 nxm of capacity is stored as 100\n  uint public constant ALLOCATION_UNITS_PER_NXM = 100;\n\n  // given capacities have 2 decimals\n  // smallest unit we can allocate is 1e18 / 100 = 1e16 = 0.01 NXM\n  uint public constant NXM_PER_ALLOCATION_UNIT = ONE_NXM / ALLOCATION_UNITS_PER_NXM;\n\n  modifier onlyCoverContract {\n    if (msg.sender != coverContract) {\n      revert OnlyCoverContract();\n    }\n    _;\n  }\n\n  modifier onlyManager {\n    if (msg.sender != manager()) {\n      revert OnlyManager();\n    }\n    _;\n  }\n\n  modifier whenNotPaused {\n    if (masterContract.isPause()) {\n      revert SystemPaused();\n    }\n    _;\n  }\n\n  modifier whenNotHalted {\n    if (isHalted) {\n      revert PoolHalted();\n    }\n    _;\n  }\n\n  constructor (\n    address _stakingNFT,\n    address _token,\n    address _coverContract,\n    address _tokenController,\n    address _master,\n    address _stakingProducts\n  ) {\n    stakingNFT = IStakingNFT(_stakingNFT);\n    nxm = INXMToken(_token);\n    coverContract = _coverContract;\n    tokenController = ITokenController(_tokenController);\n    masterContract = INXMMaster(_master);\n    stakingProducts = IStakingProducts(_stakingProducts);\n  }\n\n  function initialize(\n    bool _isPrivatePool,\n    uint _initialPoolFee,\n    uint _maxPoolFee,\n    uint _poolId,\n    string  calldata ipfsDescriptionHash\n  ) external onlyCoverContract {\n\n    if (_initialPoolFee > _maxPoolFee) {\n      revert PoolFeeExceedsMax();\n    }\n\n    if (_maxPoolFee >= 100) {\n      revert MaxPoolFeeAbove100();\n    }\n\n    isPrivatePool = _isPrivatePool;\n    poolFee = uint8(_initialPoolFee);\n    maxPoolFee = uint8(_maxPoolFee);\n    poolId = _poolId.toUint40();\n\n    emit PoolDescriptionSet(ipfsDescriptionHash);\n  }\n\n  // updateUntilCurrentTimestamp forces rewards update until current timestamp not just until\n  // bucket/tranche expiry timestamps. Must be true when changing shares or reward per second.\n  function processExpirations(bool updateUntilCurrentTimestamp) public {\n\n    uint _firstActiveBucketId = firstActiveBucketId;\n    uint _firstActiveTrancheId = firstActiveTrancheId;\n\n    uint currentBucketId = block.timestamp / BUCKET_DURATION;\n    uint currentTrancheId = block.timestamp / TRANCHE_DURATION;\n\n    // if the pool is new\n    if (_firstActiveBucketId == 0) {\n      _firstActiveBucketId = currentBucketId;\n      _firstActiveTrancheId = currentTrancheId;\n    }\n\n    // if a force update was not requested\n    if (!updateUntilCurrentTimestamp) {\n\n      bool canExpireBuckets = _firstActiveBucketId < currentBucketId;\n      bool canExpireTranches = _firstActiveTrancheId < currentTrancheId;\n\n      // and if there's nothing to expire\n      if (!canExpireBuckets && !canExpireTranches) {\n        // we can exit\n        return;\n      }\n    }\n\n    // SLOAD\n    uint _activeStake = activeStake;\n    uint _rewardPerSecond = rewardPerSecond;\n    uint _stakeSharesSupply = stakeSharesSupply;\n    uint _rewardsSharesSupply = rewardsSharesSupply;\n    uint _accNxmPerRewardsShare = accNxmPerRewardsShare;\n    uint _lastAccNxmUpdate = lastAccNxmUpdate;\n\n    // exit early if we already updated in the current block\n    if (_lastAccNxmUpdate == block.timestamp) {\n      return;\n    }\n\n    if (_rewardsSharesSupply == 0) {\n      // nothing to do, just update lastAccNxmUpdate\n      lastAccNxmUpdate = block.timestamp.toUint32();\n      return;\n    }\n\n    while (_firstActiveBucketId < currentBucketId || _firstActiveTrancheId < currentTrancheId) {\n\n      // what expires first, the bucket or the tranche?\n      bool bucketExpiresFirst;\n      {\n        uint nextBucketStart = (_firstActiveBucketId + 1) * BUCKET_DURATION;\n        uint nextTrancheStart = (_firstActiveTrancheId + 1) * TRANCHE_DURATION;\n        bucketExpiresFirst = nextBucketStart <= nextTrancheStart;\n      }\n\n      if (bucketExpiresFirst) {\n\n        // expire a bucket\n        // each bucket contains a reward reduction - we subtract it when the bucket *starts*!\n\n        ++_firstActiveBucketId;\n        uint bucketStartTime = _firstActiveBucketId * BUCKET_DURATION;\n        uint elapsed = bucketStartTime - _lastAccNxmUpdate;\n\n        uint newAccNxmPerRewardsShare = _rewardsSharesSupply != 0\n          ? elapsed * _rewardPerSecond * ONE_NXM / _rewardsSharesSupply\n          : 0;\n\n        _accNxmPerRewardsShare = _accNxmPerRewardsShare.uncheckedAdd(newAccNxmPerRewardsShare);\n\n        _rewardPerSecond -= rewardPerSecondCut[_firstActiveBucketId];\n        _lastAccNxmUpdate = bucketStartTime;\n\n        emit BucketExpired(_firstActiveBucketId - 1);\n        continue;\n      }\n\n      // expire a tranche\n      // each tranche contains shares - we expire them when the tranche *ends*\n      // TODO: check if we have to expire the tranche\n      {\n        uint trancheEndTime = (_firstActiveTrancheId + 1) * TRANCHE_DURATION;\n        uint elapsed = trancheEndTime - _lastAccNxmUpdate;\n        uint newAccNxmPerRewardsShare = _rewardsSharesSupply != 0\n          ? elapsed * _rewardPerSecond * ONE_NXM / _rewardsSharesSupply\n          : 0;\n        _accNxmPerRewardsShare = _accNxmPerRewardsShare.uncheckedAdd(newAccNxmPerRewardsShare);\n        _lastAccNxmUpdate = trancheEndTime;\n\n        // SSTORE\n        expiredTranches[_firstActiveTrancheId] = ExpiredTranche(\n          _accNxmPerRewardsShare.toUint96(), // accNxmPerRewardShareAtExpiry\n          _activeStake.toUint96(), // stakeAmountAtExpiry\n          _stakeSharesSupply.toUint128() // stakeSharesSupplyAtExpiry\n        );\n\n        // SLOAD and then SSTORE zero to get the gas refund\n        Tranche memory expiringTranche = tranches[_firstActiveTrancheId];\n        delete tranches[_firstActiveTrancheId];\n\n        // the tranche is expired now so we decrease the stake and the shares supply\n        uint expiredStake = _stakeSharesSupply != 0\n          ? (_activeStake * expiringTranche.stakeShares) / _stakeSharesSupply\n          : 0;\n\n        _activeStake -= expiredStake;\n        _stakeSharesSupply -= expiringTranche.stakeShares;\n        _rewardsSharesSupply -= expiringTranche.rewardsShares;\n\n        emit TrancheExpired(_firstActiveTrancheId);\n        // advance to the next tranche\n        _firstActiveTrancheId++;\n      }\n\n      // end while\n    }\n\n    if (updateUntilCurrentTimestamp) {\n      uint elapsed = block.timestamp - _lastAccNxmUpdate;\n      uint newAccNxmPerRewardsShare = _rewardsSharesSupply != 0\n        ? elapsed * _rewardPerSecond * ONE_NXM / _rewardsSharesSupply\n        : 0;\n      _accNxmPerRewardsShare = _accNxmPerRewardsShare.uncheckedAdd(newAccNxmPerRewardsShare);\n      _lastAccNxmUpdate = block.timestamp;\n    }\n\n    firstActiveTrancheId = _firstActiveTrancheId.toUint32();\n    firstActiveBucketId = _firstActiveBucketId.toUint32();\n\n    activeStake = _activeStake.toUint96();\n    rewardPerSecond = _rewardPerSecond.toUint96();\n    accNxmPerRewardsShare = _accNxmPerRewardsShare.toUint96();\n    lastAccNxmUpdate = _lastAccNxmUpdate.toUint32();\n    stakeSharesSupply = _stakeSharesSupply.toUint128();\n    rewardsSharesSupply = _rewardsSharesSupply.toUint128();\n  }\n\n  function depositTo(\n    uint amount,\n    uint trancheId,\n    uint requestTokenId,\n    address destination\n  ) public whenNotPaused whenNotHalted returns (uint tokenId) {\n\n    if (isPrivatePool && msg.sender != manager()) {\n      revert PrivatePool();\n    }\n\n    if (block.timestamp <= nxm.isLockedForMV(msg.sender)) {\n      revert NxmIsLockedForGovernanceVote();\n    }\n\n    {\n      uint _firstActiveTrancheId = block.timestamp / TRANCHE_DURATION;\n      uint maxTranche = _firstActiveTrancheId + MAX_ACTIVE_TRANCHES - 1;\n\n      if (amount == 0) {\n        revert InsufficientDepositAmount();\n      }\n\n      if (trancheId > maxTranche) {\n        revert RequestedTrancheIsNotYetActive();\n      }\n\n      if (trancheId < _firstActiveTrancheId) {\n        revert RequestedTrancheIsExpired();\n      }\n\n      // if the pool has no previous deposits\n      if (firstActiveTrancheId == 0) {\n        firstActiveTrancheId = _firstActiveTrancheId.toUint32();\n        firstActiveBucketId = (block.timestamp / BUCKET_DURATION).toUint32();\n        lastAccNxmUpdate = block.timestamp.toUint32();\n      } else {\n        processExpirations(true);\n      }\n    }\n\n    // storage reads\n    uint _activeStake = activeStake;\n    uint _stakeSharesSupply = stakeSharesSupply;\n    uint _rewardsSharesSupply = rewardsSharesSupply;\n    uint _accNxmPerRewardsShare = accNxmPerRewardsShare;\n    uint totalAmount;\n\n    // deposit to token id = 0 is not allowed\n    // we treat it as a flag to create a new token\n    if (requestTokenId == 0) {\n      address to = destination == address(0) ? msg.sender : destination;\n      tokenId = stakingNFT.mint(poolId, to);\n    } else {\n      // validate token id exists and belongs to this pool\n      // stakingPoolOf() reverts for non-existent tokens\n      if (stakingNFT.stakingPoolOf(requestTokenId) != poolId) {\n        revert InvalidStakingPoolForToken();\n      }\n      // validate only the token owner or an approved address can deposit\n      if (!stakingNFT.isApprovedOrOwner(msg.sender, requestTokenId)) {\n        revert NotTokenOwnerOrApproved();\n      }\n\n      tokenId = requestTokenId;\n    }\n\n    uint newStakeShares = _stakeSharesSupply == 0\n      ? Math.sqrt(amount)\n      : _stakeSharesSupply * amount / _activeStake;\n\n    uint newRewardsShares;\n\n    // update deposit and pending reward\n    {\n      // conditional read\n      Deposit memory deposit = requestTokenId == 0\n        ? Deposit(0, 0, 0, 0)\n        : deposits[tokenId][trancheId];\n\n      newRewardsShares = calculateNewRewardShares(\n        deposit.stakeShares, // initialStakeShares\n        newStakeShares, // newStakeShares\n        trancheId, // initialTrancheId\n        trancheId, // newTrancheId, the same as initialTrancheId in this case\n        block.timestamp\n      );\n\n      // if we're increasing an existing deposit\n      if (deposit.rewardsShares != 0) {\n        uint newEarningsPerShare = _accNxmPerRewardsShare.uncheckedSub(deposit.lastAccNxmPerRewardShare);\n        deposit.pendingRewards += (newEarningsPerShare * deposit.rewardsShares / ONE_NXM).toUint96();\n      }\n\n      deposit.stakeShares += newStakeShares.toUint128();\n      deposit.rewardsShares += newRewardsShares.toUint128();\n      deposit.lastAccNxmPerRewardShare = _accNxmPerRewardsShare.toUint96();\n\n      // store\n      deposits[tokenId][trancheId] = deposit;\n    }\n\n    // update pool manager's reward shares\n    {\n      Deposit memory feeDeposit = deposits[0][trancheId];\n\n      {\n        // create fee deposit reward shares\n        uint newFeeRewardShares = newRewardsShares * poolFee / (POOL_FEE_DENOMINATOR - poolFee);\n        newRewardsShares += newFeeRewardShares;\n\n        // calculate rewards until now\n        uint newRewardPerShare = _accNxmPerRewardsShare.uncheckedSub(feeDeposit.lastAccNxmPerRewardShare);\n        feeDeposit.pendingRewards += (newRewardPerShare * feeDeposit.rewardsShares / ONE_NXM).toUint96();\n        feeDeposit.lastAccNxmPerRewardShare = _accNxmPerRewardsShare.toUint96();\n        feeDeposit.rewardsShares += newFeeRewardShares.toUint128();\n      }\n\n      deposits[0][trancheId] = feeDeposit;\n    }\n\n    // update tranche\n    {\n      Tranche memory tranche = tranches[trancheId];\n      tranche.stakeShares += newStakeShares.toUint128();\n      tranche.rewardsShares += newRewardsShares.toUint128();\n      tranches[trancheId] = tranche;\n    }\n\n    totalAmount += amount;\n    _activeStake += amount;\n    _stakeSharesSupply += newStakeShares;\n    _rewardsSharesSupply += newRewardsShares;\n\n    // transfer nxm from the staker and update the pool deposit balance\n    tokenController.depositStakedNXM(msg.sender, totalAmount, poolId);\n\n    // update globals\n    activeStake = _activeStake.toUint96();\n    stakeSharesSupply = _stakeSharesSupply.toUint128();\n    rewardsSharesSupply = _rewardsSharesSupply.toUint128();\n\n    emit StakeDeposited(msg.sender, amount, trancheId, tokenId);\n  }\n\n  function getTimeLeftOfTranche(uint trancheId, uint blockTimestamp) internal pure returns (uint) {\n    uint endDate = (trancheId + 1) * TRANCHE_DURATION;\n    return endDate > blockTimestamp ? endDate - blockTimestamp : 0;\n  }\n\n  /// Calculates the amount of new reward shares based on the initial and new stake shares\n  ///\n  /// @param initialStakeShares   Amount of stake shares the deposit is already entitled to\n  /// @param stakeSharesIncrease  Amount of additional stake shares the deposit will be entitled to\n  /// @param initialTrancheId     The id of the initial tranche that defines the deposit period\n  /// @param newTrancheId         The new id of the tranche that will define the deposit period\n  /// @param blockTimestamp       The timestamp of the block when the new shares are recalculated\n  function calculateNewRewardShares(\n    uint initialStakeShares,\n    uint stakeSharesIncrease,\n    uint initialTrancheId,\n    uint newTrancheId,\n    uint blockTimestamp\n  ) public pure returns (uint) {\n\n    uint timeLeftOfInitialTranche = getTimeLeftOfTranche(initialTrancheId, blockTimestamp);\n    uint timeLeftOfNewTranche = getTimeLeftOfTranche(newTrancheId, blockTimestamp);\n\n    // the bonus is based on the the time left and the total amount of stake shares (initial + new)\n    uint newBonusShares = (initialStakeShares + stakeSharesIncrease)\n      * REWARD_BONUS_PER_TRANCHE_RATIO\n      * timeLeftOfNewTranche\n      / TRANCHE_DURATION\n      / REWARD_BONUS_PER_TRANCHE_DENOMINATOR;\n\n    // for existing deposits, the previous bonus is deducted from the final amount\n    uint previousBonusSharesDeduction = initialStakeShares\n      * REWARD_BONUS_PER_TRANCHE_RATIO\n      * timeLeftOfInitialTranche\n      / TRANCHE_DURATION\n      / REWARD_BONUS_PER_TRANCHE_DENOMINATOR;\n\n    return stakeSharesIncrease + newBonusShares - previousBonusSharesDeduction;\n  }\n\n  function withdraw(\n    uint tokenId,\n    bool withdrawStake,\n    bool withdrawRewards,\n    uint[] memory trancheIds\n  ) public whenNotPaused returns (uint withdrawnStake, uint withdrawnRewards) {\n\n    uint managerLockedInGovernanceUntil = nxm.isLockedForMV(manager());\n\n    // pass false as it does not modify the share supply nor the reward per second\n    processExpirations(true);\n\n    uint _accNxmPerRewardsShare = accNxmPerRewardsShare;\n    uint _firstActiveTrancheId = block.timestamp / TRANCHE_DURATION;\n    uint trancheCount = trancheIds.length;\n\n    for (uint j = 0; j < trancheCount; j++) {\n\n      uint trancheId = trancheIds[j];\n\n      Deposit memory deposit = deposits[tokenId][trancheId];\n\n      {\n        uint trancheRewardsToWithdraw;\n        uint trancheStakeToWithdraw;\n\n        // can withdraw stake only if the tranche is expired\n        if (withdrawStake && trancheId < _firstActiveTrancheId) {\n\n          // Deposit withdrawals are not permitted while the manager is locked in governance to\n          // prevent double voting.\n          if (managerLockedInGovernanceUntil > block.timestamp) {\n            revert ManagerNxmIsLockedForGovernanceVote();\n          }\n\n          // calculate the amount of nxm for this deposit\n          uint stake = expiredTranches[trancheId].stakeAmountAtExpiry;\n          uint _stakeSharesSupply = expiredTranches[trancheId].stakeSharesSupplyAtExpiry;\n          trancheStakeToWithdraw = stake * deposit.stakeShares / _stakeSharesSupply;\n          withdrawnStake += trancheStakeToWithdraw;\n\n          // mark as withdrawn\n          deposit.stakeShares = 0;\n        }\n\n        if (withdrawRewards) {\n\n          // if the tranche is expired, use the accumulator value saved at expiration time\n          uint accNxmPerRewardShareToUse = trancheId < _firstActiveTrancheId\n            ? expiredTranches[trancheId].accNxmPerRewardShareAtExpiry\n            : _accNxmPerRewardsShare;\n\n          // calculate reward since checkpoint\n          uint newRewardPerShare = accNxmPerRewardShareToUse.uncheckedSub(deposit.lastAccNxmPerRewardShare);\n          trancheRewardsToWithdraw = newRewardPerShare * deposit.rewardsShares / ONE_NXM + deposit.pendingRewards;\n          withdrawnRewards += trancheRewardsToWithdraw;\n\n          // save checkpoint\n          deposit.lastAccNxmPerRewardShare = accNxmPerRewardShareToUse.toUint96();\n          deposit.pendingRewards = 0;\n        }\n\n        emit Withdraw(msg.sender, tokenId, trancheId, trancheStakeToWithdraw, trancheRewardsToWithdraw);\n      }\n\n      deposits[tokenId][trancheId] = deposit;\n    }\n\n    address destination = tokenId == 0\n      ? manager()\n      : stakingNFT.ownerOf(tokenId);\n\n    tokenController.withdrawNXMStakeAndRewards(\n      destination,\n      withdrawnStake,\n      withdrawnRewards,\n      poolId\n    );\n\n    return (withdrawnStake, withdrawnRewards);\n  }\n\n  function requestAllocation(\n    uint amount,\n    uint previousPremium,\n    AllocationRequest calldata request\n  ) external onlyCoverContract returns (uint premium, uint allocationId) {\n\n    // passing true because we change the reward per second\n    processExpirations(true);\n\n    // prevent allocation requests (edits and forced expirations) for expired covers\n    if (request.allocationId != 0) {\n      uint expirationBucketId = Math.divCeil(request.previousExpiration, BUCKET_DURATION);\n      if (coverTrancheAllocations[request.allocationId] == 0 || firstActiveBucketId >= expirationBucketId) {\n        revert AlreadyDeallocated(request.allocationId);\n      }\n    }\n\n    uint[] memory trancheAllocations = request.allocationId == 0\n      ? getActiveAllocations(request.productId)\n      : getActiveAllocationsWithoutCover(\n          request.productId,\n          request.allocationId,\n          request.previousStart,\n          request.previousExpiration\n        );\n\n    // we are only deallocating\n    // rewards streaming is left as is\n    if (amount == 0) {\n      // store deallocated amount\n      updateStoredAllocations(\n        request.productId,\n        block.timestamp / TRANCHE_DURATION, // firstActiveTrancheId\n        trancheAllocations\n      );\n\n      // update coverTrancheAllocations when deallocating so we can track deallocation\n      delete coverTrancheAllocations[request.allocationId];\n      emit Deallocated(request.allocationId);\n      return (0, 0);\n    }\n\n    uint coverAllocationAmount;\n    uint initialCapacityUsed;\n    uint totalCapacity;\n    (\n      coverAllocationAmount,\n      initialCapacityUsed,\n      totalCapacity,\n      allocationId\n    ) = allocate(amount, request, trancheAllocations);\n\n    // the returned premium value has 18 decimals\n    premium = stakingProducts.getPremium(\n      poolId,\n      request.productId,\n      request.period,\n      coverAllocationAmount,\n      initialCapacityUsed,\n      totalCapacity,\n      request.globalMinPrice,\n      request.useFixedPrice,\n      NXM_PER_ALLOCATION_UNIT,\n      ALLOCATION_UNITS_PER_NXM\n    );\n\n    // add new rewards\n    {\n      if (request.rewardRatio > REWARDS_DENOMINATOR) {\n        revert RewardRatioTooHigh();\n      }\n\n      uint expirationBucket = Math.divCeil(block.timestamp + request.period, BUCKET_DURATION);\n      uint rewardStreamPeriod = expirationBucket * BUCKET_DURATION - block.timestamp;\n      uint _rewardPerSecond = (premium * request.rewardRatio / REWARDS_DENOMINATOR) / rewardStreamPeriod;\n\n      // store\n      rewardPerSecondCut[expirationBucket] += _rewardPerSecond;\n      rewardPerSecond += _rewardPerSecond.toUint96();\n\n      uint rewardsToMint = _rewardPerSecond * rewardStreamPeriod;\n      tokenController.mintStakingPoolNXMRewards(rewardsToMint, poolId);\n    }\n\n    // remove previous rewards\n    if (previousPremium > 0) {\n\n      uint prevRewards = previousPremium * request.previousRewardsRatio / REWARDS_DENOMINATOR;\n      uint prevExpirationBucket = Math.divCeil(request.previousExpiration, BUCKET_DURATION);\n      uint rewardStreamPeriod = prevExpirationBucket * BUCKET_DURATION - request.previousStart;\n      uint prevRewardsPerSecond = prevRewards / rewardStreamPeriod;\n\n      // store\n      rewardPerSecondCut[prevExpirationBucket] -= prevRewardsPerSecond;\n      rewardPerSecond -= prevRewardsPerSecond.toUint96();\n\n      // prevRewardsPerSecond * rewardStreamPeriodLeft\n      uint rewardsToBurn = prevRewardsPerSecond * (prevExpirationBucket * BUCKET_DURATION - block.timestamp);\n      tokenController.burnStakingPoolNXMRewards(rewardsToBurn, poolId);\n    }\n\n    return (premium, allocationId);\n  }\n\n  function getActiveAllocationsWithoutCover(\n    uint productId,\n    uint allocationId,\n    uint start,\n    uint expiration\n  ) internal returns (uint[] memory activeAllocations) {\n\n    uint packedCoverTrancheAllocation = coverTrancheAllocations[allocationId];\n    activeAllocations = getActiveAllocations(productId);\n\n    uint currentFirstActiveTrancheId = block.timestamp / TRANCHE_DURATION;\n    uint[] memory coverAllocations = new uint[](MAX_ACTIVE_TRANCHES);\n\n    // number of already expired tranches to skip\n    // currentFirstActiveTranche - previousFirstActiveTranche\n    uint offset = currentFirstActiveTrancheId - (start / TRANCHE_DURATION);\n\n    for (uint i = offset; i < MAX_ACTIVE_TRANCHES; i++) {\n      uint allocated = uint32(packedCoverTrancheAllocation >> (i * 32));\n      uint currentTrancheIdx = i - offset;\n      activeAllocations[currentTrancheIdx] -= allocated;\n      coverAllocations[currentTrancheIdx] = allocated;\n    }\n\n    // remove expiring cover amounts from buckets\n    updateExpiringCoverAmounts(\n      productId,\n      currentFirstActiveTrancheId,\n      Math.divCeil(expiration, BUCKET_DURATION), // targetBucketId\n      coverAllocations,\n      false // isAllocation\n    );\n\n    return activeAllocations;\n  }\n\n  function getActiveAllocations(\n    uint productId\n  ) public view returns (uint[] memory trancheAllocations) {\n\n    uint _firstActiveTrancheId = block.timestamp / TRANCHE_DURATION;\n    uint currentBucket = block.timestamp / BUCKET_DURATION;\n    uint lastBucketId;\n\n    (trancheAllocations, lastBucketId) = getStoredAllocations(productId, _firstActiveTrancheId);\n\n    if (lastBucketId == 0) {\n      lastBucketId = currentBucket;\n    }\n\n    for (uint bucketId = lastBucketId + 1; bucketId <= currentBucket; bucketId++) {\n\n      uint[] memory expirations = getExpiringCoverAmounts(productId, bucketId, _firstActiveTrancheId);\n\n      for (uint i = 0; i < MAX_ACTIVE_TRANCHES; i++) {\n        trancheAllocations[i] -= expirations[i];\n      }\n    }\n\n    return trancheAllocations;\n  }\n\n  function getStoredAllocations(\n    uint productId,\n    uint firstTrancheId\n  ) internal view returns (\n    uint[] memory storedAllocations,\n    uint16 lastBucketId\n  ) {\n\n    storedAllocations = new uint[](MAX_ACTIVE_TRANCHES);\n\n    uint firstGroupId = firstTrancheId / COVER_TRANCHE_GROUP_SIZE;\n    uint lastGroupId = (firstTrancheId + MAX_ACTIVE_TRANCHES - 1) / COVER_TRANCHE_GROUP_SIZE;\n\n    // min 2 and max 3 groups\n    uint groupCount = lastGroupId - firstGroupId + 1;\n\n    TrancheAllocationGroup[] memory allocationGroups = new TrancheAllocationGroup[](groupCount);\n\n    for (uint i = 0; i < groupCount; i++) {\n      allocationGroups[i] = trancheAllocationGroups[productId][firstGroupId + i];\n    }\n\n    lastBucketId = allocationGroups[0].getLastBucketId();\n\n    // flatten groups\n    for (uint i = 0; i < MAX_ACTIVE_TRANCHES; i++) {\n      uint trancheId = firstTrancheId + i;\n      uint trancheGroupIndex = trancheId / COVER_TRANCHE_GROUP_SIZE - firstGroupId;\n      uint trancheIndexInGroup = trancheId % COVER_TRANCHE_GROUP_SIZE;\n      storedAllocations[i] = allocationGroups[trancheGroupIndex].getItemAt(trancheIndexInGroup);\n    }\n  }\n\n  function getExpiringCoverAmounts(\n    uint productId,\n    uint bucketId,\n    uint firstTrancheId\n  ) internal view returns (uint[] memory expiringCoverAmounts) {\n\n    expiringCoverAmounts = new uint[](MAX_ACTIVE_TRANCHES);\n\n    uint firstGroupId = firstTrancheId / BUCKET_TRANCHE_GROUP_SIZE;\n    uint lastGroupId = (firstTrancheId + MAX_ACTIVE_TRANCHES - 1) / BUCKET_TRANCHE_GROUP_SIZE;\n\n    // min 1, max 2\n    uint groupCount = lastGroupId - firstGroupId + 1;\n    TrancheGroupBucket[] memory trancheGroupBuckets = new TrancheGroupBucket[](groupCount);\n\n    // min 1 and max 2 reads\n    for (uint i = 0; i < groupCount; i++) {\n      trancheGroupBuckets[i] = expiringCoverBuckets[productId][bucketId][firstGroupId + i];\n    }\n\n    // flatten bucket tranche groups\n    for (uint i = 0; i < MAX_ACTIVE_TRANCHES; i++) {\n      uint trancheId = firstTrancheId + i;\n      uint trancheGroupIndex = trancheId / BUCKET_TRANCHE_GROUP_SIZE - firstGroupId;\n      uint trancheIndexInGroup = trancheId % BUCKET_TRANCHE_GROUP_SIZE;\n      expiringCoverAmounts[i] = trancheGroupBuckets[trancheGroupIndex].getItemAt(trancheIndexInGroup);\n    }\n\n    return expiringCoverAmounts;\n  }\n\n  function getActiveTrancheCapacities(\n    uint productId,\n    uint globalCapacityRatio,\n    uint capacityReductionRatio\n  ) public view returns (\n    uint[] memory trancheCapacities,\n    uint totalCapacity\n  ) {\n\n    trancheCapacities = getTrancheCapacities(\n      productId,\n      block.timestamp / TRANCHE_DURATION, // first active tranche id\n      MAX_ACTIVE_TRANCHES,\n      globalCapacityRatio,\n      capacityReductionRatio\n    );\n\n    totalCapacity = Math.sum(trancheCapacities);\n\n    return (trancheCapacities, totalCapacity);\n  }\n\n  function getTrancheCapacities(\n    uint productId,\n    uint firstTrancheId,\n    uint trancheCount,\n    uint capacityRatio,\n    uint reductionRatio\n  ) public view returns (uint[] memory trancheCapacities) {\n\n    // will revert if with unprocessed expirations\n    if (firstTrancheId < block.timestamp / TRANCHE_DURATION) {\n      revert RequestedTrancheIsExpired();\n    }\n\n    uint _activeStake = activeStake;\n    uint _stakeSharesSupply = stakeSharesSupply;\n    trancheCapacities = new uint[](trancheCount);\n\n    if (_stakeSharesSupply == 0) {\n      return trancheCapacities;\n    }\n\n    // TODO: can we get rid of the extra call to SP here?\n    uint multiplier =\n      capacityRatio\n      * (CAPACITY_REDUCTION_DENOMINATOR - reductionRatio)\n      * stakingProducts.getProductTargetWeight(poolId, productId);\n\n    uint denominator =\n      GLOBAL_CAPACITY_DENOMINATOR\n      * CAPACITY_REDUCTION_DENOMINATOR\n      * WEIGHT_DENOMINATOR;\n\n    for (uint i = 0; i < trancheCount; i++) {\n      uint trancheStake = (_activeStake * tranches[firstTrancheId + i].stakeShares / _stakeSharesSupply);\n      trancheCapacities[i] = trancheStake * multiplier / denominator / NXM_PER_ALLOCATION_UNIT;\n    }\n\n    return trancheCapacities;\n  }\n\n  function allocate(\n    uint amount,\n    AllocationRequest calldata request,\n    uint[] memory trancheAllocations\n  ) internal returns (\n    uint coverAllocationAmount,\n    uint initialCapacityUsed,\n    uint totalCapacity,\n    uint allocationId\n  ) {\n\n    if (request.allocationId == 0) {\n      allocationId = ++lastAllocationId;\n    } else {\n      allocationId = request.allocationId;\n    }\n\n    coverAllocationAmount = Math.divCeil(amount, NXM_PER_ALLOCATION_UNIT);\n\n    uint _firstActiveTrancheId = block.timestamp / TRANCHE_DURATION;\n    uint[] memory coverAllocations = new uint[](MAX_ACTIVE_TRANCHES);\n\n    {\n      uint firstTrancheIdToUse = (block.timestamp + request.period + request.gracePeriod) / TRANCHE_DURATION;\n      uint startIndex = firstTrancheIdToUse - _firstActiveTrancheId;\n\n      uint[] memory trancheCapacities = getTrancheCapacities(\n        request.productId,\n        _firstActiveTrancheId,\n        MAX_ACTIVE_TRANCHES, // count\n        request.globalCapacityRatio,\n        request.capacityReductionRatio\n      );\n\n      uint remainingAmount = coverAllocationAmount;\n      uint carryOver;\n      uint packedCoverAllocations;\n\n      for (uint i = 0; i < startIndex; i++) {\n\n        uint allocated = trancheAllocations[i];\n        uint capacity = trancheCapacities[i];\n\n        if (allocated > capacity) {\n          carryOver += allocated - capacity;\n        } else if (carryOver > 0) {\n          carryOver -= Math.min(carryOver, capacity - allocated);\n        }\n      }\n\n      initialCapacityUsed = carryOver;\n\n      for (uint i = startIndex; i < MAX_ACTIVE_TRANCHES; i++) {\n\n        initialCapacityUsed += trancheAllocations[i];\n        totalCapacity += trancheCapacities[i];\n\n        if (trancheAllocations[i] >= trancheCapacities[i]) {\n          // carry over overallocation\n          carryOver += trancheAllocations[i] - trancheCapacities[i];\n          continue;\n        }\n\n        if (remainingAmount == 0) {\n          // not breaking out of the for loop because we need the total capacity calculated above\n          continue;\n        }\n\n        uint allocatedAmount;\n\n        {\n          uint available = trancheCapacities[i] - trancheAllocations[i];\n\n          if (carryOver > available) {\n            // no capacity left in this tranche\n            carryOver -= available;\n            continue;\n          }\n\n          available -= carryOver;\n          carryOver = 0;\n          allocatedAmount = Math.min(available, remainingAmount);\n        }\n\n        coverAllocations[i] = allocatedAmount;\n        trancheAllocations[i] += allocatedAmount;\n        remainingAmount -= allocatedAmount;\n\n        packedCoverAllocations |= allocatedAmount << i * 32;\n      }\n\n      coverTrancheAllocations[allocationId] = packedCoverAllocations;\n\n      if (remainingAmount != 0) {\n        revert InsufficientCapacity();\n      }\n    }\n\n    updateExpiringCoverAmounts(\n      request.productId,\n      _firstActiveTrancheId,\n      Math.divCeil(block.timestamp + request.period, BUCKET_DURATION), // targetBucketId\n      coverAllocations,\n      true // isAllocation\n    );\n\n    updateStoredAllocations(\n      request.productId,\n      _firstActiveTrancheId,\n      trancheAllocations\n    );\n\n    return (coverAllocationAmount, initialCapacityUsed, totalCapacity, allocationId);\n  }\n\n  function updateStoredAllocations(\n    uint productId,\n    uint firstTrancheId,\n    uint[] memory allocations\n  ) internal {\n\n    uint firstGroupId = firstTrancheId / COVER_TRANCHE_GROUP_SIZE;\n    uint lastGroupId = (firstTrancheId + MAX_ACTIVE_TRANCHES - 1) / COVER_TRANCHE_GROUP_SIZE;\n    uint groupCount = lastGroupId - firstGroupId + 1;\n\n    TrancheAllocationGroup[] memory allocationGroups = new TrancheAllocationGroup[](groupCount);\n\n    // min 2 and max 3 reads\n    for (uint i = 0; i < groupCount; i++) {\n      allocationGroups[i] = trancheAllocationGroups[productId][firstGroupId + i];\n    }\n\n    for (uint i = 0; i < MAX_ACTIVE_TRANCHES; i++) {\n\n      uint trancheId = firstTrancheId + i;\n      uint trancheGroupIndex = trancheId / COVER_TRANCHE_GROUP_SIZE - firstGroupId;\n      uint trancheIndexInGroup = trancheId % COVER_TRANCHE_GROUP_SIZE;\n\n      // setItemAt does not mutate so we have to reassign it\n      allocationGroups[trancheGroupIndex] = allocationGroups[trancheGroupIndex].setItemAt(\n        trancheIndexInGroup,\n        allocations[i].toUint48()\n      );\n    }\n\n    uint16 currentBucket = (block.timestamp / BUCKET_DURATION).toUint16();\n\n    for (uint i = 0; i < groupCount; i++) {\n      trancheAllocationGroups[productId][firstGroupId + i] = allocationGroups[i].setLastBucketId(currentBucket);\n    }\n  }\n\n  function updateExpiringCoverAmounts(\n    uint productId,\n    uint firstTrancheId,\n    uint targetBucketId,\n    uint[] memory coverTrancheAllocation,\n    bool isAllocation\n  ) internal {\n\n    uint firstGroupId = firstTrancheId / BUCKET_TRANCHE_GROUP_SIZE;\n    uint lastGroupId = (firstTrancheId + MAX_ACTIVE_TRANCHES - 1) / BUCKET_TRANCHE_GROUP_SIZE;\n    uint groupCount = lastGroupId - firstGroupId + 1;\n\n    TrancheGroupBucket[] memory trancheGroupBuckets = new TrancheGroupBucket[](groupCount);\n\n    // min 1 and max 2 reads\n    for (uint i = 0; i < groupCount; i++) {\n      trancheGroupBuckets[i] = expiringCoverBuckets[productId][targetBucketId][firstGroupId + i];\n    }\n\n    for (uint i = 0; i < MAX_ACTIVE_TRANCHES; i++) {\n\n      uint trancheId = firstTrancheId + i;\n      uint trancheGroupId = trancheId / BUCKET_TRANCHE_GROUP_SIZE - firstGroupId;\n      uint trancheIndexInGroup = trancheId % BUCKET_TRANCHE_GROUP_SIZE;\n\n      uint32 expiringAmount = trancheGroupBuckets[trancheGroupId].getItemAt(trancheIndexInGroup);\n      uint32 trancheAllocation = coverTrancheAllocation[i].toUint32();\n\n      if (isAllocation) {\n        expiringAmount += trancheAllocation;\n      } else {\n        expiringAmount -= trancheAllocation;\n      }\n\n      // setItemAt does not mutate so we have to reassign it\n      trancheGroupBuckets[trancheGroupId] = trancheGroupBuckets[trancheGroupId].setItemAt(\n        trancheIndexInGroup,\n        expiringAmount\n      );\n    }\n\n    for (uint i = 0; i < groupCount; i++) {\n      expiringCoverBuckets[productId][targetBucketId][firstGroupId + i] = trancheGroupBuckets[i];\n    }\n  }\n\n  /// Extends the period of an existing deposit until a tranche that ends further into the future\n  ///\n  /// @param tokenId           The id of the NFT that proves the ownership of the deposit.\n  /// @param initialTrancheId  The id of the tranche the deposit is already a part of.\n  /// @param newTrancheId      The id of the new tranche determining the new deposit period.\n  /// @param topUpAmount       An optional amount if the user wants to also increase the deposit\n  function extendDeposit(\n    uint tokenId,\n    uint initialTrancheId,\n    uint newTrancheId,\n    uint topUpAmount\n  ) external whenNotPaused whenNotHalted {\n\n    // token id 0 is only used for pool manager fee tracking, no deposits allowed\n    if (tokenId == 0) {\n      revert InvalidTokenId();\n    }\n\n    // validate token id exists and belongs to this pool\n    // stakingPoolOf() reverts for non-existent tokens\n    if (stakingNFT.stakingPoolOf(tokenId) != poolId) {\n      revert InvalidStakingPoolForToken();\n    }\n\n    if (isPrivatePool && msg.sender != manager()) {\n      revert PrivatePool();\n    }\n\n    if (!stakingNFT.isApprovedOrOwner(msg.sender, tokenId)) {\n      revert NotTokenOwnerOrApproved();\n    }\n\n    if (topUpAmount > 0 && block.timestamp <= nxm.isLockedForMV(msg.sender)) {\n      revert NxmIsLockedForGovernanceVote();\n    }\n\n    uint _firstActiveTrancheId = block.timestamp / TRANCHE_DURATION;\n\n    {\n      if (initialTrancheId >= newTrancheId) {\n        revert NewTrancheEndsBeforeInitialTranche();\n      }\n\n      uint maxTrancheId = _firstActiveTrancheId + MAX_ACTIVE_TRANCHES - 1;\n\n      if (newTrancheId > maxTrancheId) {\n        revert RequestedTrancheIsNotYetActive();\n      }\n\n      if (newTrancheId < firstActiveTrancheId) {\n        revert RequestedTrancheIsExpired();\n      }\n    }\n\n    // if the initial tranche is expired, withdraw everything and make a new deposit\n    // this requires the user to have grante sufficient allowance\n    if (initialTrancheId < _firstActiveTrancheId) {\n\n      uint[] memory trancheIds = new uint[](1);\n      trancheIds[0] = initialTrancheId;\n\n      (uint withdrawnStake, /* uint rewardsToWithdraw */) = withdraw(\n        tokenId,\n        true, // withdraw the deposit\n        true, // withdraw the rewards\n        trancheIds\n      );\n\n      depositTo(withdrawnStake + topUpAmount, newTrancheId, tokenId, msg.sender);\n\n      return;\n      // done! skip the rest of the function.\n    }\n\n    // if we got here - the initial tranche is still active. move all the shares to the new tranche\n\n    // passing true because we mint reward shares\n    processExpirations(true);\n\n    Deposit memory initialDeposit = deposits[tokenId][initialTrancheId];\n    Deposit memory updatedDeposit = deposits[tokenId][newTrancheId];\n\n    uint _activeStake = activeStake;\n    uint _stakeSharesSupply = stakeSharesSupply;\n    uint newStakeShares;\n\n    // calculate the new stake shares if there's a deposit top up\n    if (topUpAmount > 0) {\n      newStakeShares = _stakeSharesSupply * topUpAmount / _activeStake;\n      activeStake = (_activeStake + topUpAmount).toUint96();\n    }\n\n    // calculate the new reward shares\n    uint newRewardsShares = calculateNewRewardShares(\n      initialDeposit.stakeShares,\n      newStakeShares,\n      initialTrancheId,\n      newTrancheId,\n      block.timestamp\n    );\n\n    {\n      Tranche memory initialTranche = tranches[initialTrancheId];\n      Tranche memory newTranche = tranches[newTrancheId];\n\n      // move the shares to the new tranche\n      initialTranche.stakeShares -= initialDeposit.stakeShares;\n      initialTranche.rewardsShares -= initialDeposit.rewardsShares;\n      newTranche.stakeShares += initialDeposit.stakeShares + newStakeShares.toUint128();\n      newTranche.rewardsShares += (initialDeposit.rewardsShares + newRewardsShares).toUint128();\n\n      // store the updated tranches\n      tranches[initialTrancheId] = initialTranche;\n      tranches[newTrancheId] = newTranche;\n    }\n\n    uint _accNxmPerRewardsShare = accNxmPerRewardsShare;\n\n    // if there already is a deposit on the new tranche, calculate its pending rewards\n    if (updatedDeposit.lastAccNxmPerRewardShare != 0) {\n      uint newEarningsPerShare = _accNxmPerRewardsShare.uncheckedSub(updatedDeposit.lastAccNxmPerRewardShare);\n      updatedDeposit.pendingRewards += (newEarningsPerShare * updatedDeposit.rewardsShares / ONE_NXM).toUint96();\n    }\n\n    // calculate the rewards for the deposit being extended and move them to the new deposit\n    {\n      uint newEarningsPerShare = _accNxmPerRewardsShare.uncheckedSub(initialDeposit.lastAccNxmPerRewardShare);\n      updatedDeposit.pendingRewards += (newEarningsPerShare * initialDeposit.rewardsShares / ONE_NXM).toUint96();\n      updatedDeposit.pendingRewards += initialDeposit.pendingRewards;\n    }\n\n    updatedDeposit.lastAccNxmPerRewardShare = _accNxmPerRewardsShare.toUint96();\n    updatedDeposit.stakeShares += (initialDeposit.stakeShares + newStakeShares).toUint128();\n    updatedDeposit.rewardsShares += (initialDeposit.rewardsShares + newRewardsShares).toUint128();\n\n    // everything is moved, delete the initial deposit\n    delete deposits[tokenId][initialTrancheId];\n\n    // store the new deposit.\n    deposits[tokenId][newTrancheId] = updatedDeposit;\n\n    // update global shares supply\n    stakeSharesSupply = (_stakeSharesSupply + newStakeShares).toUint128();\n    rewardsSharesSupply += newRewardsShares.toUint128();\n\n    // transfer nxm from the staker and update the pool deposit balance\n    tokenController.depositStakedNXM(msg.sender, topUpAmount, poolId);\n\n    emit DepositExtended(msg.sender, tokenId, initialTrancheId, newTrancheId, topUpAmount);\n  }\n\n  function burnStake(uint amount, BurnStakeParams calldata params) external onlyCoverContract {\n    // passing false because neither the amount of shares nor the reward per second are changed\n    processExpirations(false);\n\n    // sload\n    uint _activeStake = activeStake;\n\n    // If all stake is burned, leave 1 wei and close pool\n    if (amount >= _activeStake) {\n      amount = _activeStake - 1;\n      isHalted = true;\n    }\n\n    tokenController.burnStakedNXM(amount, poolId);\n\n    // sstore\n    activeStake = (_activeStake - amount).toUint96();\n\n    uint initialPackedCoverTrancheAllocation = coverTrancheAllocations[params.allocationId];\n    uint[] memory activeAllocations = getActiveAllocations(params.productId);\n\n    uint currentFirstActiveTrancheId = block.timestamp / TRANCHE_DURATION;\n    uint[] memory coverDeallocations = new uint[](MAX_ACTIVE_TRANCHES);\n\n    uint remainingDeallocationAmount = params.deallocationAmount / NXM_PER_ALLOCATION_UNIT;\n    uint newPackedCoverAllocations;\n\n    // number of already expired tranches to skip\n    // currentFirstActiveTranche - previousFirstActiveTranche\n    uint offset = currentFirstActiveTrancheId - (params.start / TRANCHE_DURATION);\n\n    // iterate the tranches backward to remove allocation from future tranches first\n    for (uint i = MAX_ACTIVE_TRANCHES - 1; i >= offset; i--) {\n      // i = tranche index when the allocation was made\n      // i - offset = index of the same tranche but in currently active tranches arrays\n      uint currentTrancheIdx = i - offset;\n\n      uint allocated = uint32(initialPackedCoverTrancheAllocation >> (i * 32));\n      uint deallocateAmount = Math.min(allocated, remainingDeallocationAmount);\n\n      activeAllocations[currentTrancheIdx] -= deallocateAmount;\n      coverDeallocations[currentTrancheIdx] = deallocateAmount;\n      newPackedCoverAllocations |= (allocated - deallocateAmount) << i * 32;\n\n      remainingDeallocationAmount -= deallocateAmount;\n\n      // avoids underflow in the for decrement loop\n      if (i == 0) {\n        break;\n      }\n    }\n\n    coverTrancheAllocations[params.allocationId] = newPackedCoverAllocations;\n\n    updateExpiringCoverAmounts(\n      params.productId,\n      currentFirstActiveTrancheId,\n      Math.divCeil(params.start + params.period, BUCKET_DURATION), // targetBucketId\n      coverDeallocations,\n      false // isAllocation\n    );\n\n    updateStoredAllocations(\n      params.productId,\n      currentFirstActiveTrancheId,\n      activeAllocations\n    );\n\n    emit StakeBurned(amount);\n  }\n\n  /* pool management */\n\n  function setPoolFee(uint newFee) external onlyManager {\n\n    if (newFee > maxPoolFee) {\n      revert PoolFeeExceedsMax();\n    }\n    uint oldFee = poolFee;\n    poolFee = uint8(newFee);\n\n    // passing true because the amount of rewards shares changes\n    processExpirations(true);\n\n    uint fromTrancheId = block.timestamp / TRANCHE_DURATION;\n    uint toTrancheId = fromTrancheId + MAX_ACTIVE_TRANCHES - 1;\n    uint _accNxmPerRewardsShare = accNxmPerRewardsShare;\n\n    for (uint trancheId = fromTrancheId; trancheId <= toTrancheId; trancheId++) {\n\n      // sload\n      Deposit memory feeDeposit = deposits[0][trancheId];\n\n      if (feeDeposit.rewardsShares == 0) {\n        continue;\n      }\n\n      // update pending reward and reward shares\n      uint newRewardPerRewardsShare = _accNxmPerRewardsShare.uncheckedSub(feeDeposit.lastAccNxmPerRewardShare);\n      feeDeposit.pendingRewards += (newRewardPerRewardsShare * feeDeposit.rewardsShares / ONE_NXM).toUint96();\n      feeDeposit.lastAccNxmPerRewardShare = _accNxmPerRewardsShare.toUint96();\n      // TODO: would using tranche.rewardsShares give a better precision?\n      feeDeposit.rewardsShares = (uint(feeDeposit.rewardsShares) * newFee / oldFee).toUint128();\n\n      // sstore\n      deposits[0][trancheId] = feeDeposit;\n    }\n\n    emit PoolFeeChanged(msg.sender, newFee);\n  }\n\n  function setPoolPrivacy(bool _isPrivatePool) external onlyManager {\n    isPrivatePool = _isPrivatePool;\n    emit PoolPrivacyChanged(msg.sender, _isPrivatePool);\n  }\n\n  function setPoolDescription(string memory ipfsDescriptionHash) external onlyManager {\n    emit PoolDescriptionSet(ipfsDescriptionHash);\n  }\n\n  /* getters */\n\n  function manager() public override view returns (address) {\n    return tokenController.getStakingPoolManager(poolId);\n  }\n\n  function getPoolId() external override view returns (uint) {\n    return poolId;\n  }\n\n  function getPoolFee() external override view returns (uint) {\n    return poolFee;\n  }\n\n  function getMaxPoolFee() external override view returns (uint) {\n    return maxPoolFee;\n  }\n\n  function getActiveStake() external override view returns (uint) {\n    return activeStake;\n  }\n\n  function getStakeSharesSupply() external override view returns (uint) {\n    return stakeSharesSupply;\n  }\n\n  function getRewardsSharesSupply() external override view returns (uint) {\n    return rewardsSharesSupply;\n  }\n\n  function getRewardPerSecond() external override view returns (uint) {\n    return rewardPerSecond;\n  }\n\n  function getAccNxmPerRewardsShare() external override view returns (uint) {\n    return accNxmPerRewardsShare;\n  }\n\n  function getLastAccNxmUpdate() external override view returns (uint) {\n    return lastAccNxmUpdate;\n  }\n\n  function getFirstActiveTrancheId() external override view returns (uint) {\n    return firstActiveTrancheId;\n  }\n\n  function getFirstActiveBucketId() external override view returns (uint) {\n    return firstActiveBucketId;\n  }\n\n  function getNextAllocationId() external override view returns (uint) {\n    return lastAllocationId + 1;\n  }\n\n  function getDeposit(uint tokenId, uint trancheId) external override view returns (\n    uint lastAccNxmPerRewardShare,\n    uint pendingRewards,\n    uint stakeShares,\n    uint rewardsShares\n  ) {\n    Deposit memory deposit = deposits[tokenId][trancheId];\n    return (\n      deposit.lastAccNxmPerRewardShare,\n      deposit.pendingRewards,\n      deposit.stakeShares,\n      deposit.rewardsShares\n    );\n  }\n\n  function getTranche(uint trancheId) external override view returns (\n    uint stakeShares,\n    uint rewardsShares\n  ) {\n    Tranche memory tranche = tranches[trancheId];\n    return (\n      tranche.stakeShares,\n      tranche.rewardsShares\n    );\n  }\n\n  function getExpiredTranche(uint trancheId) external override view returns (\n    uint accNxmPerRewardShareAtExpiry,\n    uint stakeAmountAtExpiry,\n    uint stakeSharesSupplyAtExpiry\n  ) {\n    ExpiredTranche memory expiredTranche = expiredTranches[trancheId];\n    return (\n      expiredTranche.accNxmPerRewardShareAtExpiry,\n      expiredTranche.stakeAmountAtExpiry,\n      expiredTranche.stakeSharesSupplyAtExpiry\n    );\n  }\n\n}\n"
      },
      "contracts/modules/staking/StakingTypesLib.sol": {
        "content": "// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.18;\n\n// 5 x uint48 activeAllocation + 1 x uint16 lastBucketId\n// 5 * 48 + 16 = 256\ntype TrancheAllocationGroup is uint;\n\n// group ids:   ________0_________|_________1_________|_________3__ ...\n// tranche ids: 0   1   2   3   4 | 5   6   7   8   9 | 10  11  12  ...\n// active tranches:         \\________________________________/\n\n// 8 x (uint32 expiringAllocation)\ntype TrancheGroupBucket is uint;\n\nlibrary StakingTypesLib {\n\n  // TrancheAllocationGroup\n\n  function getLastBucketId(TrancheAllocationGroup items) internal pure returns (uint16) {\n    return uint16(TrancheAllocationGroup.unwrap(items));\n  }\n\n  function setLastBucketId(\n    TrancheAllocationGroup items,\n    uint16 lastBucketId\n  ) internal pure returns (TrancheAllocationGroup) {\n    // applying the mask using binary AND to clear target item's bits\n    uint mask = ~(uint(type(uint16).max));\n    uint underlying = TrancheAllocationGroup.unwrap(items);\n    return TrancheAllocationGroup.wrap(underlying & mask | uint(lastBucketId));\n  }\n\n  function getItemAt(\n    TrancheAllocationGroup items,\n    uint index\n  ) internal pure returns (uint48 allocation) {\n    uint underlying = TrancheAllocationGroup.unwrap(items);\n    return uint48(underlying >> (index * 48 + 16));\n  }\n\n  // heads up: does not mutate the TrancheAllocationGroup but returns a new one instead\n  function setItemAt(\n    TrancheAllocationGroup items,\n    uint index,\n    uint48 allocation\n  ) internal pure returns (TrancheAllocationGroup) {\n    // applying the mask using binary AND to clear target item's bits\n    uint mask = ~(uint(type(uint64).max) << (index * 48 + 16));\n    uint item = uint(allocation) << (index * 48 + 16);\n    uint underlying = TrancheAllocationGroup.unwrap(items) & mask | item;\n    return TrancheAllocationGroup.wrap(underlying);\n  }\n\n  // TrancheGroupBucket\n\n  function getItemAt(\n    TrancheGroupBucket items,\n    uint index\n  ) internal pure returns (uint32) {\n    uint underlying = TrancheGroupBucket.unwrap(items);\n    return uint32(underlying >> (index * 32));\n  }\n\n  // heads up: does not mutate the TrancheGroupBucket but returns a new one instead\n  function setItemAt(\n    TrancheGroupBucket items,\n    uint index,\n    uint32 value\n  ) internal pure returns (TrancheGroupBucket) {\n    // applying the mask using binary AND to clear target item's bits\n    uint mask = ~(uint(type(uint32).max) << (index * 32));\n    uint itemUnderlying = uint(value) << (index * 32);\n    uint groupUnderlying = TrancheGroupBucket.unwrap(items) & mask | itemUnderlying;\n    return TrancheGroupBucket.wrap(groupUnderlying);\n  }\n\n}\n"
      }
    },
    "settings": {
      "optimizer": {
        "enabled": true,
        "runs": 200
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "libraries": {}
    }
  }}