// File: src/MigrationProxy.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {ERC677ReceiverInterface} from
  "@chainlink/contracts/src/v0.8/interfaces/ERC677ReceiverInterface.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";
import {TypeAndVersionInterface} from
  "@chainlink/contracts/src/v0.8/interfaces/TypeAndVersionInterface.sol";

import {PausableWithAccessControl} from "./PausableWithAccessControl.sol";
import {CommunityStakingPool} from "./pools/CommunityStakingPool.sol";
import {OperatorStakingPool} from "./pools/OperatorStakingPool.sol";

/// @notice This contract is a proxy for migrating stakers from the Staking V0.1
/// @dev When a staker calls the migrate function on the staking v0.1 contract, it will transfer the
/// migrating LINK to this contract. This contract will then transfer the LINK to the appropriate
/// staking pool. If the user wants a partial migration, the amount to withdraw will be transferred
/// back to the staker.
/// @dev invariant LINK balance of the contract is zero
contract MigrationProxy is
  ERC677ReceiverInterface,
  PausableWithAccessControl,
  TypeAndVersionInterface
{
  /// @notice This error is thrown whenever a zero-address is supplied when
  /// a non-zero address is required
  error InvalidZeroAddress();
  /// @notice This error is thrown when the onTokenTransfer source address is
  /// not the Staking V0.1 address.
  error InvalidSourceAddress();
  /// @notice This error is thrown when the sum of amounts to stake and withdraw
  /// isn't equal to the total amount passed to the Migration Proxy.
  error InvalidAmounts(uint256 amountToStake, uint256 amountToWithdraw, uint256 amountTotal);
  /// @notice This error is thrown whenever the sender is not the LINK token
  error SenderNotLinkToken();

  /// @notice This struct defines the params required by the MigrationProxy contract's
  /// constructor.
  struct ConstructorParams {
    /// @notice The LINK Token
    LinkTokenInterface LINKAddress;
    /// @notice The Staking V0.1 Pool
    address v01StakingAddress;
    /// @notice The Operator Staking Pool
    OperatorStakingPool operatorStakingPool;
    /// @notice The Community Staker Staking Pool
    CommunityStakingPool communityStakingPool;
    /// @notice The time it requires to transfer admin role
    uint48 adminRoleTransferDelay;
  }

  /// @notice The LINK Token
  LinkTokenInterface private immutable i_LINK;
  /// @notice The Staking V0.1 Pool
  address private immutable i_v01StakingAddress;
  /// @notice The Operator Staking Pool
  OperatorStakingPool private immutable i_operatorStakingPool;
  /// @notice The Community Staking Pool
  CommunityStakingPool private immutable i_communityStakingPool;

  constructor(ConstructorParams memory params)
    PausableWithAccessControl(params.adminRoleTransferDelay, msg.sender)
  {
    if (address(params.LINKAddress) == address(0)) revert InvalidZeroAddress();
    if (address(params.v01StakingAddress) == address(0)) {
      revert InvalidZeroAddress();
    }
    if (address(params.operatorStakingPool) == address(0)) revert InvalidZeroAddress();
    if (address(params.communityStakingPool) == address(0)) revert InvalidZeroAddress();

    i_LINK = params.LINKAddress;
    i_v01StakingAddress = params.v01StakingAddress;
    i_operatorStakingPool = params.operatorStakingPool;
    i_communityStakingPool = params.communityStakingPool;
  }

  /// @notice LINK transfer callback function called when transferAndCall is called with this
  /// contract as a target.
  /// @dev precondition The v0.1 staking is closed
  /// @dev precondition The v0.2 staking pools are open
  /// @dev precondition The migration proxy is not paused
  /// @dev A redundant check for the Staking V0.1 contract being closed is omitted. This function
  /// can only be called by the V0.1 contract’s migrate function, which can
  /// only be called when the V0.1 pool is closed.
  /// @param source The Staking V0.1 address
  /// @param amount Amount of LINK token transferred
  /// @param data Bytes data received, represents migration path
  /// @inheritdoc ERC677ReceiverInterface
  function onTokenTransfer(
    address source,
    uint256 amount,
    bytes calldata data
  ) external whenNotPaused validateFromLINK {
    if (source != i_v01StakingAddress) revert InvalidSourceAddress();

    (address staker, bytes memory stakerData) = abi.decode(data, (address, bytes));

    // Full migration
    if (stakerData.length == 0) {
      _migrateToPool({staker: staker, amount: amount, data: data});
      return;
    }

    // Partial migration
    (uint256 amountToStake, uint256 amountToWithdraw) = abi.decode(stakerData, (uint256, uint256));
    if (amountToStake + amountToWithdraw != amount) {
      revert InvalidAmounts(amountToStake, amountToWithdraw, amount);
    }

    // Stake a partial amount
    _migrateToPool({staker: staker, amount: amountToStake, data: data});
    // Withdraw the rest
    // The return value is not checked since the call will revert if any balance, allowance or
    // receiver conditions fail.
    i_LINK.transfer(staker, amountToWithdraw);
  }

  /// @notice Returns the configured addresses
  /// @return The Link token, Staking V0.1, Operator staking pool, and community staking pool.
  function getConfig() external view returns (address, address, address, address) {
    return (
      address(i_LINK),
      i_v01StakingAddress,
      address(i_operatorStakingPool),
      address(i_communityStakingPool)
    );
  }

  // =======================
  // TypeAndVersionInterface
  // =======================

  /// @inheritdoc TypeAndVersionInterface
  function typeAndVersion() external pure virtual override returns (string memory) {
    return "MigrationProxy 1.0.0";
  }

  // =================
  // IERC165
  // =================

  /// @notice This function allows the calling contract to
  /// check if the contract deployed at this address is a valid
  /// LINKTokenReceiver.  A contract is a valid LINKTokenReceiver
  /// if it implements the onTokenTransfer function.
  /// @param interfaceID The ID of the interface to check against
  /// @return bool True if the contract is a valid LINKTokenReceiver.
  function supportsInterface(bytes4 interfaceID) public view override returns (bool) {
    return interfaceID == this.onTokenTransfer.selector || super.supportsInterface(interfaceID);
  }

  /// @notice Transfers the staker's funds and migration data to a staking pool.
  /// If the staker is an operator, the OperatorStakingPool will be used; otherwise,
  /// the CommunityStakingPool will be used.
  /// @param staker The staker who is migrating
  /// @param amount Amount of LINK token transferred
  /// @param data Bytes data received, represents migration path
  function _migrateToPool(address staker, uint256 amount, bytes calldata data) internal {
    address pool = i_operatorStakingPool.isOperator(staker)
      ? address(i_operatorStakingPool)
      : address(i_communityStakingPool);
    // The return value is not checked since the call will revert if any balance, allowance or
    // receiver conditions fail.
    i_LINK.transferAndCall({to: pool, value: amount, data: data});
  }

  /// @dev Reverts if not sent from the LINK token
  modifier validateFromLINK() {
    if (msg.sender != address(i_LINK)) revert SenderNotLinkToken();
    _;
  }
}

// File: lib/chainlink/contracts/src/v0.8/interfaces/ERC677ReceiverInterface.sol



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

interface ERC677ReceiverInterface {
  function onTokenTransfer(
    address sender,
    uint256 amount,
    bytes calldata data
  ) external;
}

// File: lib/chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol



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

interface LinkTokenInterface {
  function allowance(address owner, address spender) external view returns (uint256 remaining);

  function approve(address spender, uint256 value) external returns (bool success);

  function balanceOf(address owner) external view returns (uint256 balance);

  function decimals() external view returns (uint8 decimalPlaces);

  function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);

  function increaseApproval(address spender, uint256 subtractedValue) external;

  function name() external view returns (string memory tokenName);

  function symbol() external view returns (string memory tokenSymbol);

  function totalSupply() external view returns (uint256 totalTokensIssued);

  function transfer(address to, uint256 value) external returns (bool success);

  function transferAndCall(
    address to,
    uint256 value,
    bytes calldata data
  ) external returns (bool success);

  function transferFrom(
    address from,
    address to,
    uint256 value
  ) external returns (bool success);
}

// File: lib/chainlink/contracts/src/v0.8/interfaces/TypeAndVersionInterface.sol



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

abstract contract TypeAndVersionInterface {
  function typeAndVersion() external pure virtual returns (string memory);
}

// File: src/PausableWithAccessControl.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {AccessControlDefaultAdminRules} from
  "@openzeppelin/contracts/access/AccessControlDefaultAdminRules.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";

import {IPausable} from "./interfaces/IPausable.sol";

/// @notice Base contract that adds pausing and access control functionality.
abstract contract PausableWithAccessControl is IPausable, Pausable, AccessControlDefaultAdminRules {
  /// @notice This is the ID for the pauser role, which is given to the addresses that can pause and
  /// unpause the contract.
  /// @dev Hash: 65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a
  bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

  constructor(
    uint48 adminRoleTransferDelay,
    address defaultAdmin
  ) AccessControlDefaultAdminRules(adminRoleTransferDelay, defaultAdmin) {}

  /// @inheritdoc IPausable
  function emergencyPause() external onlyRole(PAUSER_ROLE) {
    _pause();
  }

  /// @inheritdoc IPausable
  function emergencyUnpause() external onlyRole(PAUSER_ROLE) {
    _unpause();
  }
}

// File: src/pools/CommunityStakingPool.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {TypeAndVersionInterface} from
  "@chainlink/contracts/src/v0.8/interfaces/TypeAndVersionInterface.sol";

import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

import {IMerkleAccessController} from "../interfaces/IMerkleAccessController.sol";
import {OperatorStakingPool} from "./OperatorStakingPool.sol";
import {StakingPoolBase} from "./StakingPoolBase.sol";

/// @notice This contract manages the staking of LINK tokens for the community stakers.
/// @dev This contract inherits the StakingPoolBase contract and interacts with the MigrationProxy,
/// OperatorStakingPool, and RewardVault contracts.
/// @dev invariant Operators cannot stake in the community staking pool.
contract CommunityStakingPool is StakingPoolBase, IMerkleAccessController, TypeAndVersionInterface {
  /// @notice This error is thrown when the pool is opened with an empty
  /// merkle root
  error MerkleRootNotSet();

  /// @notice This event is emitted when the operator staking pool is changed
  /// @param oldOperatorStakingPool The old operator staking pool
  /// @param newOperatorStakingPool The new operator staking pool
  event OperatorStakingPoolChanged(
    address indexed oldOperatorStakingPool, address indexed newOperatorStakingPool
  );

  /// @notice This struct defines the params required by the Staking contract's
  /// constructor.
  struct ConstructorParams {
    /// @notice The base staking pool constructor parameters
    ConstructorParamsBase baseParams;
    /// @notice The operator staking pool contract
    OperatorStakingPool operatorStakingPool;
  }

  /// @notice The operator staking pool contract
  OperatorStakingPool private s_operatorStakingPool;
  /// @notice The merkle root of the merkle tree generated from the list
  /// of staker addresses with early access.
  bytes32 private s_merkleRoot;

  constructor(ConstructorParams memory params) StakingPoolBase(params.baseParams) {
    if (address(params.operatorStakingPool) == address(0)) {
      revert InvalidZeroAddress();
    }

    s_operatorStakingPool = params.operatorStakingPool;
  }

  // =======================
  // IMerkleAccessController
  // =======================

  /// @inheritdoc IMerkleAccessController
  function hasAccess(address staker, bytes32[] calldata proof) external view returns (bool) {
    return _hasAccess(staker, proof);
  }

  /// @inheritdoc IMerkleAccessController
  /// @dev precondition The caller must have the initiator admin role.
  /// @dev precondition Cannot be called after the pool is closed.
  function setMerkleRoot(bytes32 newMerkleRoot) external onlyRole(INITIATOR_ROLE) whenBeforeClosing {
    bytes32 oldMerkleRoot = s_merkleRoot;
    if (oldMerkleRoot == newMerkleRoot) return;

    s_merkleRoot = newMerkleRoot;
    emit MerkleRootChanged(oldMerkleRoot, newMerkleRoot);
  }

  /// @inheritdoc IMerkleAccessController
  function getMerkleRoot() external view returns (bytes32) {
    return s_merkleRoot;
  }

  /// @notice This function sets the operator staking pool
  /// @param newOperatorStakingPool The new operator staking pool
  /// @dev precondition The caller must have the default admin role.
  function setOperatorStakingPool(OperatorStakingPool newOperatorStakingPool)
    external
    onlyRole(DEFAULT_ADMIN_ROLE)
  {
    if (address(newOperatorStakingPool) == address(0)) revert InvalidZeroAddress();
    address oldOperatorStakingPool = address(s_operatorStakingPool);
    if (oldOperatorStakingPool == address(newOperatorStakingPool)) return;

    s_operatorStakingPool = newOperatorStakingPool;
    emit OperatorStakingPoolChanged(oldOperatorStakingPool, address(newOperatorStakingPool));
  }

  // =======================
  // TypeAndVersionInterface
  // =======================

  /// @inheritdoc TypeAndVersionInterface
  function typeAndVersion() external pure virtual override returns (string memory) {
    return "CommunityStakingPool 1.0.0";
  }

  // ===============
  // StakingPoolBase
  // ===============

  /// @inheritdoc StakingPoolBase
  function _validateOnTokenTransfer(
    address sender,
    address staker,
    bytes calldata data
  ) internal view override(StakingPoolBase) {
    // check if staker has access
    // if the sender is the migration proxy, the staker is allowed to stake
    // if currently in public phase (merkle root set to empty bytes) data is ignored
    // if in the access limited phase data is the merkle proof
    // if in migrations only phase, the merkle root is set to double hash of the migration proxy
    // address. This is essentially only used as a placeholder to differentiate between the open
    // phase (empty merkle root) and access limited phase (merkle root generated from allowlist)
    if (
      sender != address(s_migrationProxy) && s_merkleRoot != bytes32(0)
        && !_hasAccess(staker, abi.decode(data, (bytes32[])))
    ) {
      revert AccessForbidden();
    }

    // check if the sender is an operator
    if (s_operatorStakingPool.isOperator(staker) || s_operatorStakingPool.isRemoved(staker)) {
      revert AccessForbidden();
    }
  }

  /// @inheritdoc StakingPoolBase
  function _validateBeforeOpen() internal view override(StakingPoolBase) {
    if (s_merkleRoot == bytes32(0)) {
      revert MerkleRootNotSet();
    }
  }

  /// @notice Util function that validates if a community staker has access to an
  /// access limited community staking pool
  /// @param staker The community staker's address
  /// @param proof Merkle proof for the community staker's allowlist
  /// @return bool True if the community staker has access to the access limited
  /// community staking pool
  function _hasAccess(address staker, bytes32[] memory proof) private view returns (bool) {
    if (s_merkleRoot == bytes32(0)) return true;
    return MerkleProof.verify({
      proof: proof,
      root: s_merkleRoot,
      leaf: keccak256(bytes.concat(keccak256(abi.encode(staker))))
    });
  }
}

// File: src/pools/OperatorStakingPool.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {TypeAndVersionInterface} from
  "@chainlink/contracts/src/v0.8/interfaces/TypeAndVersionInterface.sol";

import {AccessControlDefaultAdminRules} from
  "@openzeppelin/contracts/access/AccessControlDefaultAdminRules.sol";
import {Checkpoints} from "@openzeppelin/contracts/utils/Checkpoints.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

import {ISlashable} from "../interfaces/ISlashable.sol";
import {IRewardVault} from "../interfaces/IRewardVault.sol";
import {StakingPoolBase} from "./StakingPoolBase.sol";

/// @notice This contract manages the staking of LINK tokens for the operator stakers.
/// @dev This contract inherits the StakingPoolBase contract and interacts with the MigrationProxy,
/// PriceFeedAlertsController, CommunityStakingPool, and RewardVault contracts.
/// @dev invariant Only addresses added as operators by the contract manager can stake in this pool.
/// @dev invariant contract's LINK token balance should be greater than or equal to the sum of
/// totalPrincipal and s_alerterRewardFunds.
contract OperatorStakingPool is ISlashable, StakingPoolBase, TypeAndVersionInterface {
  using Checkpoints for Checkpoints.History;
  using EnumerableSet for EnumerableSet.AddressSet;

  /// @notice This error is raised when adding the zero address as an operator
  error InvalidOperator();
  /// @notice Error code for when the operator list is invalid
  error InvalidOperatorList();
  /// @notice Error code for when the staker is not an operator
  error StakerNotOperator();
  /// @notice This error is raised when an address is duplicated in the supplied list of operators.
  /// This can happen in addOperators and setFeedOperators functions.
  /// @param operator address of the operator
  error OperatorAlreadyExists(address operator);
  /// @notice This error is raised when removing an operator that doesn't exist.
  /// @param operator Address of the operator
  error OperatorDoesNotExist(address operator);
  /// @notice This error is raised when an operator to add has been removed previously.
  /// @param operator Address of the operator
  error OperatorHasBeenRemoved(address operator);
  /// @notice This error is raised when an operator to add is already a community staker.
  error OperatorCannotBeCommunityStaker(address operator);
  /// @notice This error is thrown whenever the max pool size is less than the
  /// reserved space for operators
  /// @param maxPoolSize The maximum pool size of the operator staking pool
  /// @param maxPrincipalPerStaker The maximum amount an operator can stake in the
  /// pool
  /// @param numOperators The number of operators in the pool
  error InsufficientPoolSpace(
    uint256 maxPoolSize, uint256 maxPrincipalPerStaker, uint256 numOperators
  );
  /// @notice This error is raised when attempting to open the staking pool with less
  /// than the minimum required node operators
  /// @param numOperators The current number of operators in the staking pool
  /// @param minInitialOperatorCount The minimum required number of operators
  /// in the staking pool before it can be opened
  error InadequateInitialOperatorCount(uint256 numOperators, uint256 minInitialOperatorCount);
  /// @notice This error is thrown when the contract manager tries to add a zero amount
  /// to the alerter reward funds
  error InvalidAlerterRewardFundAmount();
  /// @notice This error is thrown whenever the contract manager tries to withdraw
  /// more than the remaining balance in the alerter reward funds
  /// @param amountToWithdraw The amount that the contract manager tried to withdraw
  /// @param remainingBalance The remaining balance of the alerter reward funds
  error InsufficientAlerterRewardFunds(uint256 amountToWithdraw, uint256 remainingBalance);

  /// @notice This event is emitted when an operator is removed
  /// @param operator Address of the operator
  /// @param principal Operator's staked LINK amount
  /// @param newTotalPrincipal Total amount of staked juels remaining in the pool
  event OperatorRemoved(address indexed operator, uint256 principal, uint256 newTotalPrincipal);
  /// @notice This event is emitted when an operator is added
  /// @param operator Address of the operator
  event OperatorAdded(address indexed operator);
  /// @notice This event is emitted whenever the alerter reward funds is funded
  /// @param amountFunded The amount added to the alerter reward funds
  /// @param totalBalance  The current balance of the alerter reward funds
  event AlerterRewardDeposited(uint256 amountFunded, uint256 totalBalance);
  /// @notice This event is emitted whenever the contract manager withdraws from the
  /// alerter reward funds
  /// @param amountWithdrawn The amount withdrawn from the alerter reward funds
  /// @param remainingBalance The remaining balance of the alerter reward funds
  event AlerterRewardWithdrawn(uint256 amountWithdrawn, uint256 remainingBalance);
  /// @notice This event is emitted whenever the alerter is paid the full
  /// alerter reward amount
  /// @param alerter The address of the alerter
  /// @param alerterRewardActual The amount of rewards sent to the alerter in juels.
  /// This can be lower than the expected value, if the reward fund is low or we aren't able to
  /// slash enough.
  /// @param alerterRewardExpected The amount of expected rewards for the alerter
  /// in juels
  event AlertingRewardPaid(
    address indexed alerter, uint256 alerterRewardActual, uint256 alerterRewardExpected
  );
  /// @notice This event is emitted when the slasher config is set
  /// @param slasher The address of the slasher
  /// @param refillRate The refill rate of the slasher
  /// @param slashCapacity The slash capacity of the slasher
  event SlasherConfigSet(address indexed slasher, uint256 refillRate, uint256 slashCapacity);
  /// @notice This event is emitted when an operator is slashed
  /// @param operator The address of the operator
  /// @param slashedAmount The amount slashed from the operator's staked LINK
  /// amount
  /// @param updatedStakerPrincipal The operator's updated staked LINK amount
  /// @param newTotalPrincipal Total amount of staked juels remaining in the pool
  event Slashed(
    address indexed operator,
    uint256 slashedAmount,
    uint256 updatedStakerPrincipal,
    uint256 newTotalPrincipal
  );

  /// @notice This struct defines the params required by the Staking contract's
  /// constructor.
  struct ConstructorParams {
    /// @notice The base staking pool constructor parameters
    ConstructorParamsBase baseParams;
    /// @notice The minimum number of node operators required to open the
    /// staking pool.
    uint256 minInitialOperatorCount;
  }

  /// @notice This struct defines the operator-specific states.
  struct Operator {
    /// @notice The operator's staked LINK amount when they get removed.
    uint256 removedPrincipal;
    /// @notice Flag that signals whether the operator is an operator.
    bool isOperator;
    /// @notice Flag that signals whether the operator has been removed.
    bool isRemoved;
  }

  /// @notice This is the ID for the alert rewarder role, which is given to the
  /// addresses that will deposit and withdraw the alerter reward.
  /// @dev Hash: 8d2cf17e37ecc80f26d65bcf3868b78960ab38b0762747f6c5e311e75068a88b
  bytes32 public constant ALERT_REWARDER_ROLE = keccak256("ALERT_REWARDER_ROLE");
  /// @notice This is the ID for the operator manager role, which is given to the address that will
  /// add and remove operators
  /// @dev Hash: 001fdceeaab2d33566b504ecfe97e6dc3cf82cc816e696d9fe5cce35954bed17
  bytes32 public constant OPERATOR_MANAGER_ROLE = keccak256("OPERATOR_MANAGER_ROLE");
  /// @notice This is the ID for the slasher role, which will be given to the
  /// AlertsController contract.
  /// @dev Hash: 12b42e8a160f6064dc959c6f251e3af0750ad213dbecf573b4710d67d6c28e39
  bytes32 public constant SLASHER_ROLE = keccak256("SLASHER_ROLE");
  /// @notice Mapping of addresses to the Operator struct.
  mapping(address operator => Operator) private s_operators;
  /// @notice Mapping of the slashers to slasher config and state.
  mapping(address slasher => Slasher) private s_slashers;
  /// @notice The set of operators that are currently on-feed (slashable).
  EnumerableSet.AddressSet private s_operatorSet;
  /// @notice The number of node operators that have been set in the pool
  uint256 private s_numOperators;
  /// @notice Tracks the balance of the alerter reward funds.  This bucket holds all
  /// slashed funds and also funds alerter rewards.
  uint256 private s_alerterRewardFunds;
  /// @notice The minimum number of node operators required to open the
  /// staking pool.
  uint256 private immutable i_minInitialOperatorCount;

  constructor(ConstructorParams memory params) StakingPoolBase(params.baseParams) {
    i_minInitialOperatorCount = params.minInitialOperatorCount;
  }

  /// @notice Adds LINK to the alerter reward funds
  /// @param amount The amount of LINK to add to the alerter reward funds
  /// @dev precondition The caller must have the alert rewarder role.
  /// @dev precondition The caller must have at least `amount` LINK tokens.
  /// @dev precondition The caller must have approved this contract for the transfer of at least
  /// `amount` LINK tokens.
  function depositAlerterReward(uint256 amount)
    external
    onlyRole(ALERT_REWARDER_ROLE)
    whenBeforeClosing
  {
    if (amount == 0) revert InvalidAlerterRewardFundAmount();
    uint256 alerterRewardFunds = s_alerterRewardFunds;
    alerterRewardFunds += amount;
    s_alerterRewardFunds = alerterRewardFunds;
    // The return value is not checked since the call will revert if any balance, allowance or
    // receiver conditions fail.
    i_LINK.transferFrom({from: msg.sender, to: address(this), value: amount});
    emit AlerterRewardDeposited(amount, alerterRewardFunds);
  }

  /// @notice Withdraws LINK from the alerter reward funds
  /// @param amount The amount of LINK withdrawn from the alerter reward funds
  /// @dev precondition The caller must have the alert rewarder role.
  /// @dev precondition This contract must have at least `amount` LINK tokens as the alerter reward
  /// funds.
  /// @dev precondition This contract must be closed (before opening or after closing).
  function withdrawAlerterReward(uint256 amount) external onlyRole(ALERT_REWARDER_ROLE) {
    if (amount == 0) revert InvalidAlerterRewardFundAmount();
    if (s_isOpen) revert PoolNotClosed();
    uint256 alerterRewardFunds = s_alerterRewardFunds;
    if (amount > alerterRewardFunds) {
      revert InsufficientAlerterRewardFunds(amount, alerterRewardFunds);
    }
    alerterRewardFunds -= amount;
    s_alerterRewardFunds = alerterRewardFunds;
    // The return value is not checked since the call will revert if any balance, allowance or
    // receiver conditions fail.
    i_LINK.transfer(msg.sender, amount);
    emit AlerterRewardWithdrawn(amount, alerterRewardFunds);
  }

  /// @notice Returns the balance of the pool's alerter reward funds
  /// @return uint256 The balance of the pool's alerter reward funds
  function getAlerterRewardFunds() external view returns (uint256) {
    return s_alerterRewardFunds;
  }

  // ===============
  // StakingPoolBase
  // ===============

  /// @inheritdoc StakingPoolBase
  /// @dev The access control is done in StakingPoolBase.
  function setPoolConfig(
    uint256 maxPoolSize,
    uint256 maxPrincipalPerStaker
  )
    external
    override(StakingPoolBase)
    validatePoolSpace(maxPoolSize, maxPrincipalPerStaker, s_numOperators)
    whenOpen
    onlyRole(DEFAULT_ADMIN_ROLE)
  {
    _setPoolConfig(maxPoolSize, maxPrincipalPerStaker);
  }

  /// @inheritdoc StakingPoolBase
  /// @dev Removed operators need to go through the unbonding period before they can withdraw. This
  /// function will check if the operator has removed principal they can unstake.
  function unbond() external override {
    Staker storage staker = s_stakers[msg.sender];
    uint224 history = staker.history.latest();
    uint112 stakerPrincipal = uint112(history >> 112);
    if (stakerPrincipal == 0 && s_operators[msg.sender].removedPrincipal == 0) {
      revert StakeNotFound(msg.sender);
    }

    _unbond(staker);
  }

  /// @notice Registers operators from a list of unique, sorted addresses
  /// Addresses must be provided in sorted order so that
  /// address(0xNext) > address(0xPrev)
  /// @dev Previously removed operators cannot be readded to the pool.
  /// @dev precondition The caller must have the operator manager role.
  /// @dev precondition Cannot be called after the pool is closed.
  /// @param operators The sorted list of operator addresses
  function addOperators(address[] calldata operators)
    external
    whenBeforeClosing
    validateRewardVaultSet
    validatePoolSpace(
      s_pool.configs.maxPoolSize,
      s_pool.configs.maxPrincipalPerStaker,
      s_numOperators + operators.length
    )
    onlyRole(OPERATOR_MANAGER_ROLE)
  {
    for (uint256 i; i < operators.length; ++i) {
      address operatorAddress = operators[i];
      if (operatorAddress == address(0)) revert InvalidOperator();
      IRewardVault.StakerReward memory stakerReward = s_rewardVault.getStoredReward(operatorAddress);
      if (stakerReward.stakerType == IRewardVault.StakerType.COMMUNITY) {
        revert OperatorCannotBeCommunityStaker(operatorAddress);
      }
      // verify input list is sorted and addresses are unique
      if (i < operators.length - 1 && operatorAddress >= operators[i + 1]) {
        revert InvalidOperatorList();
      }
      Operator storage operator = s_operators[operatorAddress];
      if (operator.isOperator) revert OperatorAlreadyExists(operatorAddress);
      if (operator.isRemoved) revert OperatorHasBeenRemoved(operatorAddress);
      operator.isOperator = true;
      s_operatorSet.add(operatorAddress);
      emit OperatorAdded(operatorAddress);
    }

    unchecked {
      s_numOperators += operators.length;
    }
  }

  /// @notice Removes one or more operators from a list of operators.
  /// @dev Should only be callable by the owner when the pool is open.
  /// When an operator is removed, we store their staked LINK amount in a separate mapping to
  /// stop it from accruing rewards. They can withdraw their removedPrincipal and exit the system
  /// after going through the unbonding period.
  /// Removed operators are no longer slashable.
  /// @param operators A list of operator addresses to remove
  /// @dev precondition The caller must have the operator manager role.
  /// @dev precondition Cannot be called after the pool is closed.
  /// @dev precondition The operators must be currently added operators.
  function removeOperators(address[] calldata operators)
    external
    onlyRole(OPERATOR_MANAGER_ROLE)
    whenBeforeClosing
  {
    Operator storage operator;
    Staker storage staker;
    uint256 totalPrincipal = s_pool.state.totalPrincipal;
    for (uint256 i; i < operators.length; ++i) {
      address operatorAddress = operators[i];
      operator = s_operators[operatorAddress];
      if (!operator.isOperator) revert OperatorDoesNotExist(operatorAddress);

      staker = s_stakers[operatorAddress];
      uint224 history = staker.history.latest();
      uint256 principal = uint256(history >> 112);
      uint256 stakedAtTime = uint112(history);
      s_rewardVault.concludeRewardPeriod({
        staker: operatorAddress,
        oldPrincipal: principal,
        unstakedAmount: principal,
        shouldForfeit: true,
        stakedAt: stakedAtTime
      });

      totalPrincipal -= principal;
      s_pool.state.totalPrincipal = totalPrincipal;
      delete operator.isOperator;
      s_operatorSet.remove(operatorAddress);
      operator.isRemoved = true;
      // Reset the staker's stakedAtTime to 0 so their multiplier resets to 0.
      _updateStakerHistory({staker: staker, latestPrincipal: 0, latestStakedAtTime: 0});
      // Move the operator's staked LINK amount to removedPrincipal so that
      // the operator stops earning rewards
      operator.removedPrincipal = principal;

      _resetUnbondingPeriod(staker, operatorAddress);

      emit OperatorRemoved(operatorAddress, principal, totalPrincipal);
    }

    s_numOperators -= operators.length;
  }

  /// @notice Getter function to check if an address is registered as an operator
  /// @param staker The address of the staker
  /// @return bool True if the staker is an operator
  function isOperator(address staker) external view returns (bool) {
    return s_operators[staker].isOperator;
  }

  /// @notice Getter function to check if an address is a removed operator
  /// @param staker The address of the staker
  /// @return bool True if the operator has been removed
  function isRemoved(address staker) external view returns (bool) {
    return s_operators[staker].isRemoved;
  }

  /// @notice Getter function for a removed operator's total staked LINK amount
  /// @param staker The address of the staker
  /// @return uint256 The removed operator's staked LINK amount that hasn't been withdrawn
  function getRemovedPrincipal(address staker) external view returns (uint256) {
    return s_operators[staker].removedPrincipal;
  }

  /// @notice Called by removed operators to withdraw their removed stake
  /// @dev precondition The caller must be in the claim period or the pool must be closed or paused.
  /// @dev precondition The caller must be a removed operator with some removed
  /// staked LINK amount.
  function unstakeRemovedPrincipal() external {
    if (!_canUnstake(s_stakers[msg.sender])) {
      revert StakerNotInClaimPeriod(msg.sender);
    }

    uint256 withdrawableAmount = s_operators[msg.sender].removedPrincipal;
    if (withdrawableAmount == 0) {
      revert UnstakeExceedsPrincipal();
    }
    delete s_operators[msg.sender].removedPrincipal;

    // The return value is not checked since the call will revert if any balance, allowance or
    // receiver conditions fail.
    i_LINK.transfer(msg.sender, withdrawableAmount);
    // Since operator has been removed their total amount staked will be 0
    emit Unstaked(msg.sender, withdrawableAmount, 0, s_pool.state.totalPrincipal);
  }

  /// @notice Returns the number of operators configured in the pool.
  /// @return uint256 The number of operators configured in the pool
  function getNumOperators() external view returns (uint256) {
    return s_numOperators;
  }

  /// @notice Returns the list of operators configured in the pool.
  /// @return address[] The list of operators configured in the pool
  function getOperators() external view returns (address[] memory) {
    return s_operatorSet.values();
  }

  // =======================
  // ISlashable
  // =======================

  /// @inheritdoc ISlashable
  /// @dev precondition The caller must have the default admin role.
  /// @dev precondition Cannot be called after the pool is closed.
  function addSlasher(
    address slasher,
    SlasherConfig calldata config
  ) external onlyRole(DEFAULT_ADMIN_ROLE) whenBeforeClosing {
    _grantRole(SLASHER_ROLE, slasher);
    _setSlasherConfig(slasher, config);
  }

  /// @inheritdoc ISlashable
  /// @dev precondition The caller must have the default admin role.
  /// @dev precondition Cannot be called after the pool is closed.
  function removeSlasher(address slasher) external onlyRole(DEFAULT_ADMIN_ROLE) whenBeforeClosing {
    if (!hasRole(SLASHER_ROLE, slasher)) {
      revert InvalidSlasher();
    }
    delete s_slashers[slasher];
    _revokeRole(SLASHER_ROLE, slasher);
    emit SlasherConfigSet(slasher, 0, 0);
  }

  /// @inheritdoc ISlashable
  /// @dev precondition The caller must have the default admin role.
  /// @dev precondition Cannot be called after the pool is closed.
  function setSlasherConfig(
    address slasher,
    SlasherConfig calldata config
  ) external onlyRole(DEFAULT_ADMIN_ROLE) whenBeforeClosing {
    if (!hasRole(SLASHER_ROLE, slasher)) {
      revert InvalidSlasher();
    }
    _setSlasherConfig(slasher, config);
  }

  /// @inheritdoc ISlashable
  function getSlasherConfig(address slasher) external view returns (SlasherConfig memory) {
    return s_slashers[slasher].config;
  }

  /// @inheritdoc ISlashable
  function getSlashCapacity(address slasher) external view returns (uint256) {
    SlasherConfig memory slasherConfig = s_slashers[slasher].config;
    return _getRemainingSlashCapacity(slasherConfig, slasher);
  }

  /// @inheritdoc ISlashable
  /// @dev In the current implementation, on-feed operators can raise alerts to rescue a portion of
  /// their slashed staked LINK amount. All operators can raise alerts in the priority period. Note
  /// that this may change in the future as we add alerting for additional services.
  /// @dev We will operationally make sure to remove an operator from the slashable (on-feed)
  /// operators list in alerts controllers if they are removed from the operators list in this
  /// contract, so there won't be a case where we slash a removed operator.
  /// @dev precondition The caller must have the slasher role.
  /// @dev precondition This contract must be active (open and stakers are earning rewards).
  /// @dev precondition The slasher must have enough capacity to slash.
  function slashAndReward(
    address[] calldata stakers,
    address alerter,
    uint256 principalAmount,
    uint256 alerterRewardAmount
  ) external onlySlasher whenActive whenNotPaused {
    SlasherConfig storage slasherConfig = s_slashers[msg.sender].config;
    uint256 combinedSlashAmount = stakers.length * principalAmount;

    uint256 remainingSlashCapacity = _getRemainingSlashCapacity(slasherConfig, msg.sender);
    // check if the total slashed amount exceeds the slasher's capacity
    if (combinedSlashAmount > remainingSlashCapacity) {
      /// @dev If a slashing occurs with an amount to be slashed that is higher than the remaining
      /// slashing capacity, only an amount equal to the remaining capacity is slashed.
      principalAmount = remainingSlashCapacity / stakers.length;
    }

    uint256 totalSlashedAmount = _slashOperators(stakers, principalAmount);

    s_slashers[msg.sender].state.remainingSlashCapacityAmount =
      remainingSlashCapacity - totalSlashedAmount;
    s_slashers[msg.sender].state.lastSlashTimestamp = block.timestamp;

    _payAlerter({
      alerter: alerter,
      totalSlashedAmount: totalSlashedAmount,
      alerterRewardAmount: alerterRewardAmount
    });
  }

  // =======================
  // TypeAndVersionInterface
  // =======================

  /// @inheritdoc TypeAndVersionInterface
  function typeAndVersion() external pure virtual override returns (string memory) {
    return "OperatorStakingPool 1.0.0";
  }

  // ==============================
  // AccessControlDefaultAdminRules
  // ==============================

  /// @inheritdoc AccessControlDefaultAdminRules
  /// @notice Grants `role` to `account`. Reverts if the contract manager tries to grant the default
  /// admin or slasher role.
  /// @dev The default admin role must be granted through `beginDefaultAdminTransfer` and
  /// `acceptDefaultAdminTransfer`.
  /// @dev The slasher role must be granted through `addSlasher`.
  /// @param role The role to grant
  /// @param account The address to grant the role to
  function grantRole(
    bytes32 role,
    address account
  ) public virtual override(AccessControlDefaultAdminRules) {
    if (role == SLASHER_ROLE) revert InvalidRole();
    super.grantRole(role, account);
  }

  /// @inheritdoc AccessControlDefaultAdminRules
  /// @notice Grants `role` to `account`. Reverts if the contract manager tries to grant the default
  /// admin or slasher role.
  /// @dev The default admin role must be revoked through `beginDefaultAdminTransfer` and
  /// `acceptDefaultAdminTransfer` to another address.
  /// @dev The slasher role must be revoked through `removeSlasher`.
  /// @param role The role to revoke
  /// @param account The address to revoke the role from
  function revokeRole(
    bytes32 role,
    address account
  ) public virtual override(AccessControlDefaultAdminRules) {
    if (role == SLASHER_ROLE) revert InvalidRole();
    super.revokeRole(role, account);
  }

  // ===============
  // StakingPoolBase
  // ===============

  /// @inheritdoc StakingPoolBase
  function _validateOnTokenTransfer(
    address,
    address staker,
    bytes calldata
  ) internal view override(StakingPoolBase) {
    // check if staker is an operator
    if (!s_operators[staker].isOperator) revert StakerNotOperator();
  }

  /// @inheritdoc StakingPoolBase
  function _validateBeforeOpen() internal view override(StakingPoolBase) {
    if (s_numOperators < i_minInitialOperatorCount) {
      revert InadequateInitialOperatorCount(s_numOperators, i_minInitialOperatorCount);
    }
  }

  /// @notice Helper function to set the slasher config
  /// @param slasher The slasher
  /// @param config The slasher config
  function _setSlasherConfig(address slasher, SlasherConfig calldata config) private {
    if (config.slashCapacity == 0 || config.refillRate == 0) {
      revert ISlashable.InvalidSlasherConfig();
    }

    s_slashers[slasher].config = config;

    // refill capacity
    SlasherState storage state = s_slashers[slasher].state;
    state.remainingSlashCapacityAmount = config.slashCapacity;
    state.lastSlashTimestamp = block.timestamp;

    emit SlasherConfigSet(slasher, config.refillRate, config.slashCapacity);
  }

  /// @notice Helper function to slash operators
  /// @param operators The list of operators to slash
  /// @param principalAmount The amount to slash from each operator's staked
  /// LINK amount
  /// @return The total amount slashed from all operators
  function _slashOperators(
    address[] calldata operators,
    uint256 principalAmount
  ) private returns (uint256) {
    // perform the slash on all operators and add up the total slashed amount
    uint256 totalSlashedAmount;
    Staker storage staker;
    uint256 totalPrincipal = s_pool.state.totalPrincipal;
    for (uint256 i; i < operators.length; ++i) {
      // verify input list is sorted and addresses are unique
      address operatorAddress = operators[i];
      if (i < operators.length - 1 && operatorAddress >= operators[i + 1]) {
        revert InvalidOperatorList();
      }
      staker = s_stakers[operatorAddress];
      uint224 history = staker.history.latest();
      uint256 operatorPrincipal = uint112(history >> 112);
      uint256 stakerStakedAtTime = uint112(history);
      uint256 slashedAmount =
        principalAmount > operatorPrincipal ? operatorPrincipal : principalAmount;
      uint256 updatedPrincipal = operatorPrincipal - slashedAmount;

      // update the staker's rewards
      s_rewardVault.updateReward(operatorAddress, operatorPrincipal);
      _updateStakerHistory({
        staker: staker,
        latestPrincipal: updatedPrincipal,
        latestStakedAtTime: stakerStakedAtTime
      });

      totalSlashedAmount += slashedAmount;
      totalPrincipal -= slashedAmount;

      emit Slashed(operatorAddress, slashedAmount, updatedPrincipal, totalPrincipal);
    }
    // update the pool state
    s_pool.state.totalPrincipal = totalPrincipal;

    return totalSlashedAmount;
  }

  /// @notice Helper function to reward the alerter
  /// @param alerter The alerter
  /// @param totalSlashedAmount The total amount slashed from all the operators
  /// @param alerterRewardAmount The amount to reward the alerter
  function _payAlerter(
    address alerter,
    uint256 totalSlashedAmount,
    uint256 alerterRewardAmount
  ) private {
    uint256 newAlerterRewardFunds = s_alerterRewardFunds + totalSlashedAmount;
    uint256 alerterRewardActual =
      newAlerterRewardFunds < alerterRewardAmount ? newAlerterRewardFunds : alerterRewardAmount;
    s_alerterRewardFunds = newAlerterRewardFunds - alerterRewardActual;

    // We emit an event here instead of reverting so that the alerter can
    // immediately receive a portion of their rewards.  This event
    // will allow the contract manager to reimburse any remaining rewards to the
    // alerter.
    emit AlertingRewardPaid(alerter, alerterRewardActual, alerterRewardAmount);

    // The return value is not checked since the call will revert if any balance, allowance or
    // receiver conditions fail.
    i_LINK.transfer(alerter, alerterRewardActual);
  }

  /// @notice Helper function to return the current remaining slash capacity for a slasher
  /// @param slasherConfig The slasher's config
  /// @param slasher The slasher
  /// @return The remaining slashing capacity
  function _getRemainingSlashCapacity(
    SlasherConfig memory slasherConfig,
    address slasher
  ) private view returns (uint256) {
    SlasherState memory slasherState = s_slashers[slasher].state;
    uint256 refilledAmount =
      (block.timestamp - slasherState.lastSlashTimestamp) * slasherConfig.refillRate;

    return Math.min(
      slasherConfig.slashCapacity, slasherState.remainingSlashCapacityAmount + refilledAmount
    );
  }

  /// @dev Reverts if the msg.sender doesn't have the rewarder role.
  modifier onlyRewarder() {
    if (!hasRole(ALERT_REWARDER_ROLE, msg.sender)) {
      revert AccessForbidden();
    }
    _;
  }

  /// @dev Reverts if not sent by an address that has the SLASHER role
  modifier onlySlasher() {
    if (!hasRole(SLASHER_ROLE, msg.sender)) {
      revert AccessForbidden();
    }
    _;
  }

  /// @notice Checks that the maximum pool size is greater than or equal to
  /// the reserved space for operators.
  /// @param maxPoolSize The maximum pool size of the operator staking pool
  /// @param maxPrincipalPerStaker The maximum amount an operator can stake in the
  /// @param numOperators The number of operators in the pool
  /// @dev The reserved space is calculated by multiplying the number of
  /// operators and the maximum staked LINK amount per operator
  modifier validatePoolSpace(
    uint256 maxPoolSize,
    uint256 maxPrincipalPerStaker,
    uint256 numOperators
  ) {
    if (maxPoolSize < maxPrincipalPerStaker * numOperators) {
      revert InsufficientPoolSpace(maxPoolSize, maxPrincipalPerStaker, numOperators);
    }
    _;
  }
}

// File: lib/openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol



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

pragma solidity ^0.8.0;

import "./AccessControl.sol";
import "./IAccessControlDefaultAdminRules.sol";
import "../utils/math/SafeCast.sol";
import "../interfaces/IERC5313.sol";

/**
 * @dev Extension of {AccessControl} that allows specifying special rules to manage
 * the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions
 * over other roles that may potentially have privileged rights in the system.
 *
 * If a specific role doesn't have an admin role assigned, the holder of the
 * `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it.
 *
 * This contract implements the following risk mitigations on top of {AccessControl}:
 *
 * * Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced.
 * * Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account.
 * * Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted.
 * * The delay can be changed by scheduling, see {changeDefaultAdminDelay}.
 * * It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`.
 *
 * Example usage:
 *
 * ```solidity
 * contract MyToken is AccessControlDefaultAdminRules {
 *   constructor() AccessControlDefaultAdminRules(
 *     3 days,
 *     msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder
 *    ) {}
 * }
 * ```
 *
 * _Available since v4.9._
 */
abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRules, IERC5313, AccessControl {
    // pending admin pair read/written together frequently
    address private _pendingDefaultAdmin;
    uint48 private _pendingDefaultAdminSchedule; // 0 == unset

    uint48 private _currentDelay;
    address private _currentDefaultAdmin;

    // pending delay pair read/written together frequently
    uint48 private _pendingDelay;
    uint48 private _pendingDelaySchedule; // 0 == unset

    /**
     * @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address.
     */
    constructor(uint48 initialDelay, address initialDefaultAdmin) {
        require(initialDefaultAdmin != address(0), "AccessControl: 0 default admin");
        _currentDelay = initialDelay;
        _grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin);
    }

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

    /**
     * @dev See {IERC5313-owner}.
     */
    function owner() public view virtual returns (address) {
        return defaultAdmin();
    }

    ///
    /// Override AccessControl role management
    ///

    /**
     * @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
     */
    function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
        require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly grant default admin role");
        super.grantRole(role, account);
    }

    /**
     * @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
     */
    function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
        require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly revoke default admin role");
        super.revokeRole(role, account);
    }

    /**
     * @dev See {AccessControl-renounceRole}.
     *
     * For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling
     * {beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule
     * has also passed when calling this function.
     *
     * After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions.
     *
     * NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin},
     * thereby disabling any functionality that is only available for it, and the possibility of reassigning a
     * non-administrated role.
     */
    function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
        if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
            (address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin();
            require(
                newDefaultAdmin == address(0) && _isScheduleSet(schedule) && _hasSchedulePassed(schedule),
                "AccessControl: only can renounce in two delayed steps"
            );
            delete _pendingDefaultAdminSchedule;
        }
        super.renounceRole(role, account);
    }

    /**
     * @dev See {AccessControl-_grantRole}.
     *
     * For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the
     * role has been previously renounced.
     *
     * NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE`
     * assignable again. Make sure to guarantee this is the expected behavior in your implementation.
     */
    function _grantRole(bytes32 role, address account) internal virtual override {
        if (role == DEFAULT_ADMIN_ROLE) {
            require(defaultAdmin() == address(0), "AccessControl: default admin already granted");
            _currentDefaultAdmin = account;
        }
        super._grantRole(role, account);
    }

    /**
     * @dev See {AccessControl-_revokeRole}.
     */
    function _revokeRole(bytes32 role, address account) internal virtual override {
        if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
            delete _currentDefaultAdmin;
        }
        super._revokeRole(role, account);
    }

    /**
     * @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override {
        require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't violate default admin rules");
        super._setRoleAdmin(role, adminRole);
    }

    ///
    /// AccessControlDefaultAdminRules accessors
    ///

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function defaultAdmin() public view virtual returns (address) {
        return _currentDefaultAdmin;
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function pendingDefaultAdmin() public view virtual returns (address newAdmin, uint48 schedule) {
        return (_pendingDefaultAdmin, _pendingDefaultAdminSchedule);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function defaultAdminDelay() public view virtual returns (uint48) {
        uint48 schedule = _pendingDelaySchedule;
        return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? _pendingDelay : _currentDelay;
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function pendingDefaultAdminDelay() public view virtual returns (uint48 newDelay, uint48 schedule) {
        schedule = _pendingDelaySchedule;
        return (_isScheduleSet(schedule) && !_hasSchedulePassed(schedule)) ? (_pendingDelay, schedule) : (0, 0);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function defaultAdminDelayIncreaseWait() public view virtual returns (uint48) {
        return 5 days;
    }

    ///
    /// AccessControlDefaultAdminRules public and internal setters for defaultAdmin/pendingDefaultAdmin
    ///

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        _beginDefaultAdminTransfer(newAdmin);
    }

    /**
     * @dev See {beginDefaultAdminTransfer}.
     *
     * Internal function without access restriction.
     */
    function _beginDefaultAdminTransfer(address newAdmin) internal virtual {
        uint48 newSchedule = SafeCast.toUint48(block.timestamp) + defaultAdminDelay();
        _setPendingDefaultAdmin(newAdmin, newSchedule);
        emit DefaultAdminTransferScheduled(newAdmin, newSchedule);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        _cancelDefaultAdminTransfer();
    }

    /**
     * @dev See {cancelDefaultAdminTransfer}.
     *
     * Internal function without access restriction.
     */
    function _cancelDefaultAdminTransfer() internal virtual {
        _setPendingDefaultAdmin(address(0), 0);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function acceptDefaultAdminTransfer() public virtual {
        (address newDefaultAdmin, ) = pendingDefaultAdmin();
        require(_msgSender() == newDefaultAdmin, "AccessControl: pending admin must accept");
        _acceptDefaultAdminTransfer();
    }

    /**
     * @dev See {acceptDefaultAdminTransfer}.
     *
     * Internal function without access restriction.
     */
    function _acceptDefaultAdminTransfer() internal virtual {
        (address newAdmin, uint48 schedule) = pendingDefaultAdmin();
        require(_isScheduleSet(schedule) && _hasSchedulePassed(schedule), "AccessControl: transfer delay not passed");
        _revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin());
        _grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
        delete _pendingDefaultAdmin;
        delete _pendingDefaultAdminSchedule;
    }

    ///
    /// AccessControlDefaultAdminRules public and internal setters for defaultAdminDelay/pendingDefaultAdminDelay
    ///

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        _changeDefaultAdminDelay(newDelay);
    }

    /**
     * @dev See {changeDefaultAdminDelay}.
     *
     * Internal function without access restriction.
     */
    function _changeDefaultAdminDelay(uint48 newDelay) internal virtual {
        uint48 newSchedule = SafeCast.toUint48(block.timestamp) + _delayChangeWait(newDelay);
        _setPendingDelay(newDelay, newSchedule);
        emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        _rollbackDefaultAdminDelay();
    }

    /**
     * @dev See {rollbackDefaultAdminDelay}.
     *
     * Internal function without access restriction.
     */
    function _rollbackDefaultAdminDelay() internal virtual {
        _setPendingDelay(0, 0);
    }

    /**
     * @dev Returns the amount of seconds to wait after the `newDelay` will
     * become the new {defaultAdminDelay}.
     *
     * The value returned guarantees that if the delay is reduced, it will go into effect
     * after a wait that honors the previously set delay.
     *
     * See {defaultAdminDelayIncreaseWait}.
     */
    function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) {
        uint48 currentDelay = defaultAdminDelay();

        // When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up
        // to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day
        // to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new
        // delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like
        // using milliseconds instead of seconds.
        //
        // When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees
        // that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled.
        // For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days.
        return
            newDelay > currentDelay
                ? uint48(Math.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48
                : currentDelay - newDelay;
    }

    ///
    /// Private setters
    ///

    /**
     * @dev Setter of the tuple for pending admin and its schedule.
     *
     * May emit a DefaultAdminTransferCanceled event.
     */
    function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private {
        (, uint48 oldSchedule) = pendingDefaultAdmin();

        _pendingDefaultAdmin = newAdmin;
        _pendingDefaultAdminSchedule = newSchedule;

        // An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted.
        if (_isScheduleSet(oldSchedule)) {
            // Emit for implicit cancellations when another default admin was scheduled.
            emit DefaultAdminTransferCanceled();
        }
    }

    /**
     * @dev Setter of the tuple for pending delay and its schedule.
     *
     * May emit a DefaultAdminDelayChangeCanceled event.
     */
    function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private {
        uint48 oldSchedule = _pendingDelaySchedule;

        if (_isScheduleSet(oldSchedule)) {
            if (_hasSchedulePassed(oldSchedule)) {
                // Materialize a virtual delay
                _currentDelay = _pendingDelay;
            } else {
                // Emit for implicit cancellations when another delay was scheduled.
                emit DefaultAdminDelayChangeCanceled();
            }
        }

        _pendingDelay = newDelay;
        _pendingDelaySchedule = newSchedule;
    }

    ///
    /// Private helpers
    ///

    /**
     * @dev Defines if an `schedule` is considered set. For consistency purposes.
     */
    function _isScheduleSet(uint48 schedule) private pure returns (bool) {
        return schedule != 0;
    }

    /**
     * @dev Defines if an `schedule` is considered passed. For consistency purposes.
     */
    function _hasSchedulePassed(uint48 schedule) private view returns (bool) {
        return schedule < block.timestamp;
    }
}

// File: lib/openzeppelin-contracts/contracts/security/Pausable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

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

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

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

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

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

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

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

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

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

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

// File: src/interfaces/IPausable.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IPausable {
  /// @notice This function pauses the contract
  /// @dev Sets the pause flag to true
  function emergencyPause() external;

  /// @notice This function unpauses the contract
  /// @dev Sets the pause flag to false
  function emergencyUnpause() external;
}

// File: lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates merkle trees that are safe
 * against this attack out of the box.
 */
library MerkleProof {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     *
     * _Available since v4.7._
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     *
     * _Available since v4.7._
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * _Available since v4.7._
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            require(proofPos == proofLen, "MerkleProof: invalid multiproof");
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            require(proofPos == proofLen, "MerkleProof: invalid multiproof");
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

// File: src/interfaces/IMerkleAccessController.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IMerkleAccessController {
  /// @notice Emitted when the contract owner updates the staking allowlist
  /// @param oldMerkleRoot The root of the old Staking allowlist merkle tree
  /// @param newMerkleRoot The root of a new Staking allowlist merkle tree
  event MerkleRootChanged(bytes32 oldMerkleRoot, bytes32 newMerkleRoot);

  /// @notice Validates if a community staker has access to the private staking pool
  /// @param staker The community staker's address
  /// @param proof Merkle proof for the community staker's allowlist
  /// @return true If the staker has access to the private staking pool
  function hasAccess(address staker, bytes32[] calldata proof) external view returns (bool);

  /// @notice This function is called to update the staking allowlist in a private staking pool
  /// @dev Only callable by the contract owner
  /// @param newMerkleRoot Merkle Tree root, used to prove access for community stakers
  /// will be required at opening but can be removed at any time by the owner when
  /// staking access will be granted to the public.
  function setMerkleRoot(bytes32 newMerkleRoot) external;

  /// @notice This function returns the current root of the Staking allowlist merkle tree
  /// @return The current root of the Staking allowlist merkle tree
  function getMerkleRoot() external view returns (bytes32);
}

// File: src/pools/StakingPoolBase.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {ERC677ReceiverInterface} from
  "@chainlink/contracts/src/v0.8/interfaces/ERC677ReceiverInterface.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
import {Checkpoints} from "@openzeppelin/contracts/utils/Checkpoints.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

import {IMigratable} from "../interfaces/IMigratable.sol";
import {IRewardVault} from "../interfaces/IRewardVault.sol";
import {IStakingOwner} from "../interfaces/IStakingOwner.sol";
import {IStakingPool} from "../interfaces/IStakingPool.sol";
import {Migratable} from "../Migratable.sol";
import {PausableWithAccessControl} from "../PausableWithAccessControl.sol";

/// @notice This contract is the base contract for staking pools. Each staking pool extends this
/// contract.
/// @dev This contract is abstract and must be inherited.
/// @dev invariant maxPoolSize must be greater than or equal to the totalPrincipal.
/// @dev invariant maxPoolSize must be greater than or equal to the maxPrincipalPerStaker.
/// @dev invariant contract's LINK token balance should be greater than or equal to the
/// totalPrincipal.
/// @dev invariant The migrated staked LINK amount must be less than or equal to the staker's staked
/// LINK amount +
/// rewards from the v0.1 staking pool.
/// @dev invariant The migrated staked LINK amount must be less than or equal to the
/// maxPrincipalPerStaker.
/// @dev We only support LINK token in v0.2 staking. Rebasing tokens, ERC777 tokens, fee-on-transfer
/// tokens or tokens that do not have 18 decimal places are not supported.
abstract contract StakingPoolBase is
  ERC677ReceiverInterface,
  IStakingPool,
  IStakingOwner,
  Migratable,
  PausableWithAccessControl
{
  using Checkpoints for Checkpoints.History;
  using SafeCast for uint256;

  /// @notice This error is thrown when the staking pool is not active.
  error PoolNotActive();

  /// @notice This error is thrown when the unbonding period is set to 0
  error InvalidUnbondingPeriod();

  /// @notice This error is thrown when the claim period is set to 0
  error InvalidClaimPeriod();

  /// @notice This error is thrown whenever a staker tries to unbond during
  /// their unbonding period.
  /// @param unbondingPeriodEndsAt The time the unbonding period is finished
  error UnbondingOrClaimPeriodActive(uint256 unbondingPeriodEndsAt);

  /// @notice This error is thrown whenever a staker tries to unstake outside
  /// the claim period
  /// @param staker The staker trying to unstake
  error StakerNotInClaimPeriod(address staker);

  /// @notice This error is thrown when an invalid claim period range is provided
  /// @param minClaimPeriod The min claim period
  /// @param maxClaimPeriod The max claim period
  error InvalidClaimPeriodRange(uint256 minClaimPeriod, uint256 maxClaimPeriod);

  /// @notice This error is thrown when an invalid max unbonding period is provided
  /// @param maxUnbondingPeriod The max unbonding period
  error InvalidMaxUnbondingPeriod(uint256 maxUnbondingPeriod);

  /// @notice This error is thrown when a staker tries to stake and the reward vault connected to
  /// this pool is not open or is paused
  error RewardVaultNotActive();

  /// @notice This error is thrown when admin tries to open the pool and the reward vault connected
  /// to this pool has not had rewards added to it.
  error RewardVaultHasNoRewards();

  /// @notice This error is thrown when admin tries to set a new reward vault and the old reward
  /// vault is not closed yet.
  error RewardVaultNotClosed();

  /// @notice This event is emitted whenever a staker initiates the unbonding
  /// period.
  /// @param staker The staker that has started their unbonding period.
  event UnbondingPeriodStarted(address indexed staker);

  /// @notice This event is emitted when a staker's unbonding period is reset
  /// @param staker The staker that has reset their unbonding period
  event UnbondingPeriodReset(address indexed staker);

  /// @notice This event is emitted when the unbonding period has been changed
  /// @param oldUnbondingPeriod The old unbonding period
  /// @param newUnbondingPeriod The new unbonding period
  event UnbondingPeriodSet(uint256 oldUnbondingPeriod, uint256 newUnbondingPeriod);

  /// @notice This event is emitted when the claim period is set
  /// @param oldClaimPeriod The old claim period
  /// @param newClaimPeriod The new claim period
  event ClaimPeriodSet(uint256 oldClaimPeriod, uint256 newClaimPeriod);

  /// @notice This event is emitted when the reward vault is set
  /// @param oldRewardVault The old reward vault
  /// @param newRewardVault The new reward vault
  event RewardVaultSet(address indexed oldRewardVault, address indexed newRewardVault);

  /// @notice This event is emitted when the staker is migrated to the migration target
  /// @param migrationTarget The migration target
  /// @param amount The staker's staked LINK amount that was migrated in juels
  /// @param migrationData The migration data
  event StakerMigrated(address indexed migrationTarget, uint256 amount, bytes migrationData);

  /// @notice This struct defines the params required by the Staking contract's
  /// constructor.
  struct ConstructorParamsBase {
    /// @notice The LINK Token
    LinkTokenInterface LINKAddress;
    /// @notice The initial maximum total stake amount for all stakers in the
    /// pool
    uint96 initialMaxPoolSize;
    /// @notice The initial maximum stake amount for a staker
    uint96 initialMaxPrincipalPerStaker;
    /// @notice The minimum stake amount that a staker must stake
    uint96 minPrincipalPerStaker;
    /// @notice The initial unbonding period
    uint32 initialUnbondingPeriod;
    /// @notice The max value that the unbonding period can be set to
    uint32 maxUnbondingPeriod;
    /// @notice The initial claim period
    uint32 initialClaimPeriod;
    /// @notice The min value that the claim period can be set to
    uint32 minClaimPeriod;
    /// @notice The max value that the claim period can be set to
    uint32 maxClaimPeriod;
    /// @notice The time it requires to transfer admin role
    uint48 adminRoleTransferDelay;
  }

  /// @notice This struct defines the params that the pool is configured with
  struct PoolConfigs {
    /// @notice The max amount of staked LINK allowed in the pool in juels. The max value of this
    /// field is expected to be less than 1 billion (10^9 * 10^18), which is less than the max value
    /// that can be represented by a uint96 (~7.9*10^28).
    uint96 maxPoolSize;
    /// @notice The max amount of LINK a staker can stake in juels. The max value of this field is
    /// expected to be less than 1 million (10^6 * 10^18), which is less than the max value that can
    /// be represented by a uint96 (~7.9*10^28).
    uint96 maxPrincipalPerStaker;
    /// @notice The length of the unbonding period in seconds. The max value of this field is
    /// expected to be less than a year, or 30 million (3.2*10^7), which is less than the max value
    /// that can be represented by a uint32 (~4.2*10^9).
    uint32 unbondingPeriod;
    /// @notice The length of the claim period in seconds. The max value of this field is
    /// expected to be less than a year, or 30 million (3.2*10^7), which is less than the max value
    /// that can be represented by a uint32 (~4.2*10^9).
    uint32 claimPeriod;
  }

  /// @notice This struct defines the state of the staking pool
  struct PoolState {
    /// @notice The total staked LINK amount amount in the pool
    uint256 totalPrincipal;
    /// @notice The time that the pool was closed
    uint256 closedAt;
  }

  /// @notice This struct defines the global state and configuration of the pool
  struct Pool {
    /// @notice The pool's configuration
    PoolConfigs configs;
    /// @notice The pool's state
    PoolState state;
  }

  /// @notice This is the ID for the initiator role, which is given to the
  /// addresses that will add open the pools, and set the merkle root for the community pool.
  /// @dev Hash: 6b8b15f1c11543d8280deaa7c24d12fffba6a357e4428e8c43e4234790186bff
  bytes32 public constant INITIATOR_ROLE = keccak256("INITIATOR_ROLE");
  /// @notice The LINK token
  LinkTokenInterface internal immutable i_LINK;
  /// @notice The staking pool state and configuration
  Pool internal s_pool;
  /// @notice Mapping of a staker's address to their staker state
  mapping(address staker => IStakingPool.Staker) internal s_stakers;
  /// @notice Migration proxy address
  address internal s_migrationProxy;
  /// @notice The latest reward vault address
  IRewardVault internal s_rewardVault;
  /// @notice The min amount of LINK that a staker can stake
  uint96 internal immutable i_minPrincipalPerStaker;
  /// @notice The min value that the claim period can be set to
  uint32 private immutable i_minClaimPeriod;
  /// @notice The max value that the claim period can be set to
  uint32 private immutable i_maxClaimPeriod;
  /// @notice The max value that the unbonding period can be set to
  uint32 private immutable i_maxUnbondingPeriod;
  /// @notice Flag that signals if the staking pool is open for staking
  bool internal s_isOpen;

  constructor(ConstructorParamsBase memory params)
    PausableWithAccessControl(params.adminRoleTransferDelay, msg.sender)
  {
    if (address(params.LINKAddress) == address(0)) revert InvalidZeroAddress();
    if (params.minPrincipalPerStaker == 0) revert InvalidMinStakeAmount();
    if (params.minPrincipalPerStaker >= params.initialMaxPrincipalPerStaker) {
      revert InvalidMinStakeAmount();
    }
    if (params.maxUnbondingPeriod == 0) {
      revert InvalidMaxUnbondingPeriod(params.maxUnbondingPeriod);
    }
    if (params.minClaimPeriod == 0 || params.minClaimPeriod >= params.maxClaimPeriod) {
      revert InvalidClaimPeriodRange(params.minClaimPeriod, params.maxClaimPeriod);
    }

    i_LINK = params.LINKAddress;
    i_minPrincipalPerStaker = params.minPrincipalPerStaker;

    i_maxUnbondingPeriod = params.maxUnbondingPeriod;
    _setUnbondingPeriod(params.initialUnbondingPeriod);

    _setPoolConfig(params.initialMaxPoolSize, params.initialMaxPrincipalPerStaker);

    i_minClaimPeriod = params.minClaimPeriod;
    i_maxClaimPeriod = params.maxClaimPeriod;
    _setClaimPeriod(params.initialClaimPeriod);
  }

  /// @inheritdoc IMigratable
  /// @dev This will migrate the staker's staked LINK
  /// @dev precondition This contract must be closed and upgraded to a new pool.
  /// @dev precondition The migration target must be set.
  /// @dev precondition The caller must be staked in the pool.
  function migrate(bytes calldata data) external whenClosed validateMigrationTargetSet {
    // must be in storage to get access to latest()
    IStakingPool.Staker storage staker = s_stakers[msg.sender];

    uint224 history = staker.history.latest();
    uint112 stakerPrincipal = uint112(history >> 112);
    uint112 stakerStakedAtTime = uint112(history);
    if (stakerPrincipal == 0) revert StakeNotFound(msg.sender);

    bytes memory migrationData = abi.encode(msg.sender, stakerStakedAtTime, data);

    // Finalize staker's rewards to include any rewards they have earned before resetting the
    // principal and stakedAtTime.
    s_rewardVault.concludeRewardPeriod({
      staker: msg.sender,
      oldPrincipal: stakerPrincipal,
      stakedAt: stakerStakedAtTime,
      unstakedAmount: stakerPrincipal,
      shouldForfeit: false
    });
    s_pool.state.totalPrincipal -= stakerPrincipal;

    // do not reset staked at time to not reset the multiplier because staker is not forfeiting
    // rewards when migrating
    _updateStakerHistory({
      staker: staker,
      latestPrincipal: 0,
      latestStakedAtTime: stakerStakedAtTime
    });
    // The return value is not checked since the call will revert if any balance, allowance or
    // receiver conditions fail.
    i_LINK.transferAndCall({to: s_migrationTarget, value: stakerPrincipal, data: migrationData});
    emit StakerMigrated(s_migrationTarget, stakerPrincipal, migrationData);
  }

  /// @notice Starts the unbonding period for the staker.  A staker may unstake
  /// their staked LINK during the claim period that follows the unbonding period.
  /// @dev precondition The caller must be staked in the pool.
  /// @dev precondition The caller must not be in an unbonding period.
  /// @dev precondition The caller must not be in a claim period.
  function unbond() external virtual {
    Staker storage staker = s_stakers[msg.sender];
    uint224 history = staker.history.latest();
    uint112 stakerPrincipal = uint112(history >> 112);
    if (stakerPrincipal == 0) revert StakeNotFound(msg.sender);

    _unbond(staker);
  }

  /// @notice Sets the new unbonding period for the pool.  Stakers that are
  /// already unbonding will not be affected.
  /// @param newUnbondingPeriod The new unbonding period
  /// @dev precondition The caller must have the default admin role.
  /// @dev precondition Cannot be called after the pool is closed.
  function setUnbondingPeriod(uint256 newUnbondingPeriod)
    external
    onlyRole(DEFAULT_ADMIN_ROLE)
    whenBeforeClosing
  {
    _setUnbondingPeriod(newUnbondingPeriod);
  }

  /// @notice Returns the max unbonding period
  /// @return uint256 The max value that the unbonding period can be set to
  function getMaxUnbondingPeriod() external view returns (uint256) {
    return (i_maxUnbondingPeriod);
  }

  /// @notice Set the claim period
  /// @param claimPeriod The claim period
  /// @dev precondition Cannot be called after the pool is closed.
  function setClaimPeriod(uint256 claimPeriod)
    external
    onlyRole(DEFAULT_ADMIN_ROLE)
    whenBeforeClosing
  {
    _setClaimPeriod(claimPeriod);
  }

  /// @notice Sets the new reward vault for the pool
  /// @param newRewardVault The new reward vault
  /// @dev precondition The caller must have the default admin role.
  /// @dev precondition Cannot be called after the pool is closed.
  function setRewardVault(IRewardVault newRewardVault)
    external
    onlyRole(DEFAULT_ADMIN_ROLE)
    whenBeforeClosing
  {
    if (address(newRewardVault) == address(0)) revert InvalidZeroAddress();
    address oldRewardVault = address(s_rewardVault);
    if (oldRewardVault == address(newRewardVault)) return;
    if (address(s_rewardVault) != address(0) && s_rewardVault.isOpen()) {
      revert RewardVaultNotClosed();
    }
    if (
      address(s_rewardVault) != address(0)
        && (!newRewardVault.isOpen() || newRewardVault.isPaused())
    ) revert RewardVaultNotActive();
    if (address(s_rewardVault) != address(0) && !newRewardVault.hasRewardAdded()) {
      revert RewardVaultHasNoRewards();
    }

    s_rewardVault = newRewardVault;
    emit RewardVaultSet(oldRewardVault, address(newRewardVault));
  }

  /// @notice LINK transfer callback function called when transferAndCall is called with this
  /// contract as a target.
  /// @param sender staker's address if they stake into the pool by calling transferAndCall on the
  /// LINK token, or MigrationProxy contract when a staker migrates from V0.1 to V0.2
  /// @param amount Amount of LINK token transferred
  /// @param data Bytes data received, represents migration path
  /// @inheritdoc ERC677ReceiverInterface
  /// @dev precondition The migration proxy must be set.
  /// @dev precondition This contract must be open and not paused.
  /// @dev precondition The reward vault must be open and not paused.
  function onTokenTransfer(
    address sender,
    uint256 amount,
    bytes calldata data
  ) external validateFromLINK validateMigrationProxySet whenOpen whenRewardVaultOpen whenNotPaused {
    if (amount == 0) return;

    // Check if this call was forwarded from the migration proxy.
    address staker = sender == s_migrationProxy ? _getStakerAddress(data) : sender;
    if (staker == address(0)) revert InvalidZeroAddress();

    // includes access check for non migration proxy
    _validateOnTokenTransfer(sender, staker, data);

    Staker storage stakerState = s_stakers[staker];
    uint224 history = stakerState.history.latest();
    uint256 stakerPrincipal = uint256(history >> 112);
    uint256 stakedAt = uint112(history);

    _resetUnbondingPeriod(stakerState, staker);

    s_rewardVault.concludeRewardPeriod({
      staker: staker,
      oldPrincipal: stakerPrincipal,
      unstakedAmount: 0,
      shouldForfeit: false,
      stakedAt: stakedAt
    });

    _increaseStake(staker, stakerPrincipal + amount, amount);
  }

  /// @notice Returns the minimum and maximum claim periods that can be set by the owner
  /// @return uint256 minimum claim period
  /// @return uint256 maximum claim period
  function getClaimPeriodLimits() external view returns (uint256, uint256) {
    return (i_minClaimPeriod, i_maxClaimPeriod);
  }

  // =================
  // IStakingOwner
  // =================

  /// @inheritdoc IStakingOwner
  /// @dev precondition The caller must have the default admin role.
  function setPoolConfig(
    uint256 maxPoolSize,
    uint256 maxPrincipalPerStaker
  ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) whenOpen {
    _setPoolConfig(maxPoolSize, maxPrincipalPerStaker);
  }

  /// @inheritdoc IStakingOwner
  /// @dev precondition The caller must have the initiator role.
  function open()
    external
    onlyRole(INITIATOR_ROLE)
    whenBeforeOpening
    validateRewardVaultSet
    whenRewardVaultOpen
    whenRewardVaultHasRewards
  {
    _validateBeforeOpen();
    s_isOpen = true;
    emit PoolOpened();
  }

  /// @inheritdoc IStakingOwner
  /// @dev precondition The caller must have the default admin role.
  function close() external onlyRole(DEFAULT_ADMIN_ROLE) whenOpen {
    s_isOpen = false;
    s_pool.state.closedAt = block.timestamp;
    emit PoolClosed();
  }

  /// @inheritdoc IStakingOwner
  /// @dev precondition The caller must have the default admin role.
  function setMigrationProxy(address migrationProxy)
    external
    onlyRole(DEFAULT_ADMIN_ROLE)
    whenBeforeClosing
  {
    if (migrationProxy == address(0)) revert InvalidZeroAddress();

    if (s_migrationProxy == migrationProxy) return;
    address oldMigrationProxy = s_migrationProxy;
    s_migrationProxy = migrationProxy;

    emit MigrationProxySet(oldMigrationProxy, migrationProxy);
  }

  // =================
  // IStakingPool
  // =================

  /// @inheritdoc IStakingPool
  /// @dev precondition The caller must be staked in the pool.
  /// @dev precondition The caller must be in the claim period or the pool must be closed or paused.
  /// @dev There is a possible reentrancy attack here where a malicious admin
  /// can point this pool to a malicious reward vault that calls unstake on the
  /// pool again.  This reentrancy attack is possible as the pool updates the
  /// staker's staked LINK amount after it calls concludeRewardPeriod on the configured reward
  /// vault.  This scenario is mitigated by forcing the admin to go through
  /// a timelock period that is longer than the unbonding period, which will
  /// provide stakers sufficient time to withdraw their staked LINK from the
  /// pool before a malicious reward vault is set.
  function unstake(uint256 amount) external {
    // cannot unstake 0
    if (amount == 0) revert UnstakeZeroAmount();

    Staker storage staker = s_stakers[msg.sender];
    if (!_canUnstake(staker)) {
      revert StakerNotInClaimPeriod(msg.sender);
    }

    uint224 history = staker.history.latest();
    uint256 stakerPrincipal = uint256(history >> 112);
    uint256 stakedAt = uint112(history);
    // verify that the staker has enough staked LINK amount to unstake
    if (amount > stakerPrincipal) revert UnstakeExceedsPrincipal();

    uint256 updatedPrincipal = stakerPrincipal - amount;
    // in the case of a partial withdrawal, verify new staked LINK amount is above minimum
    if (amount < stakerPrincipal && updatedPrincipal < i_minPrincipalPerStaker) {
      revert UnstakePrincipalBelowMinAmount();
    }

    s_rewardVault.concludeRewardPeriod({
      staker: msg.sender,
      oldPrincipal: stakerPrincipal,
      unstakedAmount: amount,
      shouldForfeit: true,
      stakedAt: stakedAt
    });

    s_pool.state.totalPrincipal -= amount;

    // Reset the staker's staked at time to 0 to prevent the multiplier
    // from growing if the staker has unstaked all their staked LINK
    _updateStakerHistory({
      staker: staker,
      latestPrincipal: updatedPrincipal,
      latestStakedAtTime: updatedPrincipal == 0 ? 0 : block.timestamp
    });
    // The return value is not checked since the call will revert if any balance, allowance or
    // receiver conditions fail.
    i_LINK.transfer(msg.sender, amount);

    emit Unstaked(msg.sender, amount, updatedPrincipal, s_pool.state.totalPrincipal);
  }

  /// @inheritdoc IStakingPool
  function getTotalPrincipal() external view returns (uint256) {
    return s_pool.state.totalPrincipal;
  }

  /// @inheritdoc IStakingPool
  function getStakerPrincipal(address staker) external view returns (uint256) {
    return uint112(s_stakers[staker].history.latest() >> 112);
  }

  /// @inheritdoc IStakingPool
  function getStakerPrincipalAt(
    address staker,
    uint256 blockNumber
  ) external view returns (uint256) {
    // `Checkpoints` requires to exclude the current block when calling `getAtBlock`
    return (blockNumber == block.number)
      ? uint112(s_stakers[staker].history.latest() >> 112)
      : uint112(s_stakers[staker].history.getAtBlock(blockNumber) >> 112);
  }

  /// @inheritdoc IStakingPool
  function getStakerStakedAtTime(address staker) external view returns (uint256) {
    return uint112(s_stakers[staker].history.latest());
  }

  /// @inheritdoc IStakingPool
  function getStakerStakedAtTimeAt(
    address staker,
    uint256 blockNumber
  ) external view returns (uint256) {
    // `Checkpoints` requires to exclude the current block when calling `getAtBlock`
    return (blockNumber == block.number)
      ? uint112(s_stakers[staker].history.latest())
      : uint112(s_stakers[staker].history.getAtBlock(blockNumber));
  }

  /// @inheritdoc IStakingPool
  function getRewardVault() external view returns (IRewardVault) {
    return s_rewardVault;
  }

  /// @inheritdoc IStakingPool
  function getChainlinkToken() external view returns (address) {
    return address(i_LINK);
  }

  /// @inheritdoc IStakingPool
  function getMigrationProxy() external view returns (address) {
    return s_migrationProxy;
  }

  /// @inheritdoc IStakingPool
  function isOpen() external view returns (bool) {
    return s_isOpen;
  }

  /// @inheritdoc IStakingPool
  function isActive() external view returns (bool) {
    return _isActive();
  }

  /// @inheritdoc IStakingPool
  function getStakerLimits() external view returns (uint256, uint256) {
    return (i_minPrincipalPerStaker, s_pool.configs.maxPrincipalPerStaker);
  }

  /// @inheritdoc IStakingPool
  function getMaxPoolSize() external view returns (uint256) {
    return s_pool.configs.maxPoolSize;
  }

  /// @notice Returns the time a staker's unbonding period ends
  /// @param staker The address of the staker to query
  /// @return uint256 The timestamp of when the staker's unbonding period ends.
  /// This value will be 0 if the unbonding period is not active.
  function getUnbondingEndsAt(address staker) external view returns (uint256) {
    return s_stakers[staker].unbondingPeriodEndsAt;
  }

  /// @notice Returns the pool's unbonding parameters
  /// @return uint256 The pool's unbonding period
  /// @return uint256 The pools's claim period
  function getUnbondingParams() external view returns (uint256, uint256) {
    return (s_pool.configs.unbondingPeriod, s_pool.configs.claimPeriod);
  }

  /// @notice Returns the time a staker's claim period ends
  /// @param staker The staker trying to unstake their staked LINK
  /// @return uint256 The timestamp of when the staker's claim period ends.
  /// This value will be 0 if the unbonding period has not started.
  function getClaimPeriodEndsAt(address staker) external view returns (uint256) {
    return s_stakers[staker].claimPeriodEndsAt;
  }

  // ===============
  // ERC165
  // ===============

  /// @notice This function allows the calling contract to
  /// check if the contract deployed at this address is a valid
  /// LINKTokenReceiver.  A contract is a valid LINKTokenReceiver
  /// if it implements the onTokenTransfer function.
  /// @param interfaceID The ID of the interface to check against
  /// @return bool True if the contract is a valid LINKTokenReceiver.
  function supportsInterface(bytes4 interfaceID) public view override returns (bool) {
    return interfaceID == this.onTokenTransfer.selector || super.supportsInterface(interfaceID);
  }

  // =========
  // Helpers
  // =========

  /// @notice Resets a staker's unbonding period
  /// @param stakerState The staker's current state
  /// @param staker The address of the staker to reset the unbonding period for
  /// @dev This sets the stakerState's unbondingPeriodEndsAt and
  /// claimPeriodEndsAt to 0
  function _resetUnbondingPeriod(Staker storage stakerState, address staker) internal {
    if (stakerState.unbondingPeriodEndsAt != 0) {
      delete stakerState.unbondingPeriodEndsAt;
      delete stakerState.claimPeriodEndsAt;
      emit UnbondingPeriodReset(staker);
    }
  }

  /// @inheritdoc Migratable
  /// @dev precondition The migration target must implement the onTokenTransfer function.
  /// @dev precondition Cannot be called after the pool is closed.
  function _validateMigrationTarget(address newMigrationTarget) internal override whenBeforeClosing {
    Migratable._validateMigrationTarget(newMigrationTarget);
    if (
      !IERC165(newMigrationTarget).supportsInterface(
        ERC677ReceiverInterface.onTokenTransfer.selector
      )
    ) {
      revert InvalidMigrationTarget();
    }
  }

  /// @notice Validate for when LINK is staked or migrated into the pool
  /// @param sender The address transferring LINK into the pool. Could be the migration proxy
  /// contract or the staker.
  /// @param staker The address staking or migrating LINK into the pool
  /// @param data Arbitrary data passed when staking or migrating
  function _validateOnTokenTransfer(
    address sender,
    address staker,
    bytes calldata data
  ) internal view virtual;

  /// @notice Validates pool state before opening
  function _validateBeforeOpen() internal view virtual;

  /// @notice Util function for setting the pool config
  /// @param maxPoolSize The max amount of staked LINK allowed in the pool
  /// @param maxPrincipalPerStaker The max amount of LINK a staker can stake
  /// in the pool.
  function _setPoolConfig(uint256 maxPoolSize, uint256 maxPrincipalPerStaker) internal {
    PoolConfigs storage configs = s_pool.configs;
    // only allow increasing the maxPoolSize
    if (maxPoolSize == 0 || maxPoolSize < configs.maxPoolSize) {
      revert InvalidPoolSize(maxPoolSize);
    }
    // only allow increasing the maxPrincipalPerStaker
    if (
      maxPrincipalPerStaker == 0 || maxPrincipalPerStaker > maxPoolSize
        || configs.maxPrincipalPerStaker > maxPrincipalPerStaker
    ) revert InvalidMaxStakeAmount(maxPrincipalPerStaker);

    if (configs.maxPoolSize != maxPoolSize) {
      configs.maxPoolSize = maxPoolSize.toUint96();
      emit PoolSizeIncreased(maxPoolSize);
    }
    if (configs.maxPrincipalPerStaker != maxPrincipalPerStaker) {
      configs.maxPrincipalPerStaker = maxPrincipalPerStaker.toUint96();
      emit MaxPrincipalAmountIncreased(maxPrincipalPerStaker);
    }
  }

  /// @notice Util function for setting the unbonding period
  /// @param unbondingPeriod The unbonding period
  function _setUnbondingPeriod(uint256 unbondingPeriod) internal {
    if (unbondingPeriod == 0 || unbondingPeriod > i_maxUnbondingPeriod) {
      revert InvalidUnbondingPeriod();
    }

    if (s_pool.configs.unbondingPeriod == unbondingPeriod) return;

    uint256 oldUnbondingPeriod = s_pool.configs.unbondingPeriod;
    s_pool.configs.unbondingPeriod = unbondingPeriod.toUint32();
    emit UnbondingPeriodSet(oldUnbondingPeriod, unbondingPeriod);
  }

  /// @notice Updates the staking pool state and the staker state
  /// @param sender The staker address
  /// @param newPrincipal The staker's staked LINK amount after staking
  /// @param amount The amount to stake
  function _increaseStake(address sender, uint256 newPrincipal, uint256 amount) internal {
    Staker storage staker = s_stakers[sender];

    // validate staking limits
    if (newPrincipal < i_minPrincipalPerStaker) {
      revert InsufficientStakeAmount();
    }
    if (newPrincipal > s_pool.configs.maxPrincipalPerStaker) {
      revert ExceedsMaxStakeAmount();
    }
    uint256 newTotalPrincipal = s_pool.state.totalPrincipal + amount;
    if (newTotalPrincipal > s_pool.configs.maxPoolSize) {
      revert ExceedsMaxPoolSize();
    }

    // update the pool state
    s_pool.state.totalPrincipal = newTotalPrincipal;

    // update the staker state
    _updateStakerHistory({
      staker: staker,
      latestPrincipal: newPrincipal,
      latestStakedAtTime: block.timestamp
    });

    emit Staked(sender, amount, newPrincipal, newTotalPrincipal);
  }

  /// @notice Gets the staker address from the data passed by the MigrationProxy contract
  /// @param data The data passed by the MigrationProxy contract
  /// @return The staker address
  function _getStakerAddress(bytes calldata data) internal pure returns (address) {
    if (data.length == 0) revert InvalidData();

    // decode the data
    (address staker) = abi.decode(data, (address));

    return staker;
  }

  /// @notice Checks to see whether or not a staker is eligible to
  /// unstake their staked LINK amount (when the pool is closed or, when the pool is open and they
  /// are in the claim period or, when pool is paused)
  /// @param staker The staker trying to unstake their staked LINK
  /// @return bool True if the staker is eligible to unstake
  function _canUnstake(Staker storage staker) internal view returns (bool) {
    return s_pool.state.closedAt != 0 || _inClaimPeriod(staker) || paused();
  }

  /// @notice Updates the staker's staked LINK amount history
  /// @param staker The staker to update
  /// @param latestPrincipal The staker's latest staked LINK amount
  /// @param latestStakedAtTime The staker's latest average staked at time
  function _updateStakerHistory(
    Staker storage staker,
    uint256 latestPrincipal,
    uint256 latestStakedAtTime
  ) internal {
    staker.history.push(
      (uint224(uint112(latestPrincipal)) << 112) | uint224(uint112(latestStakedAtTime))
    );
  }

  /// @notice Starts the unbonding period for the staker
  /// @param staker The staker trying to unbond
  function _unbond(Staker storage staker) internal {
    if (staker.unbondingPeriodEndsAt != 0 && block.timestamp <= staker.claimPeriodEndsAt) {
      revert UnbondingOrClaimPeriodActive(staker.unbondingPeriodEndsAt);
    }
    staker.unbondingPeriodEndsAt = (block.timestamp + s_pool.configs.unbondingPeriod).toUint128();
    staker.claimPeriodEndsAt = staker.unbondingPeriodEndsAt + s_pool.configs.claimPeriod;
    emit UnbondingPeriodStarted(msg.sender);
  }

  /// @notice Checks to see whether or not a staker is within the claim period
  /// to unstake their staked LINK
  /// @param staker The staker trying to unstake their staked LINK
  /// @return bool True if the staker is inside the claim period
  function _inClaimPeriod(Staker storage staker) private view returns (bool) {
    if (staker.unbondingPeriodEndsAt == 0 || block.timestamp < staker.unbondingPeriodEndsAt) {
      return false;
    }

    return block.timestamp <= staker.claimPeriodEndsAt;
  }

  /// @notice Util function for setting the claim period
  /// @param claimPeriod The claim period
  function _setClaimPeriod(uint256 claimPeriod) private {
    if (claimPeriod < i_minClaimPeriod || claimPeriod > i_maxClaimPeriod) {
      revert InvalidClaimPeriod();
    }

    if (s_pool.configs.claimPeriod == claimPeriod) return;

    uint256 oldClaimPeriod = s_pool.configs.claimPeriod;
    s_pool.configs.claimPeriod = claimPeriod.toUint32();

    emit ClaimPeriodSet(oldClaimPeriod, claimPeriod);
  }

  /// @notice Util function to check if the reward vault connected to this pool has rewards added to
  /// it
  /// @return bool True if the reward vault has rewards added to it, false otherwise
  function _hasRewardVaultRewardAdded() internal view virtual returns (bool) {
    return s_rewardVault.hasRewardAdded();
  }

  /// @notice Util function to check if the pool is active
  /// @return bool True if the pool is active, false otherwise
  function _isActive() internal view returns (bool) {
    return s_isOpen && !s_rewardVault.hasRewardDurationEnded(address(this));
  }

  // =========
  // Modifiers
  // =========

  /// @dev Reverts if not sent from the LINK token
  modifier validateFromLINK() {
    if (msg.sender != address(i_LINK)) revert SenderNotLinkToken();
    _;
  }

  /// @dev Reverts if migration proxy is not set
  modifier validateMigrationProxySet() {
    if (s_migrationProxy == address(0)) revert MigrationProxyNotSet();
    _;
  }

  /// @dev Reverts if reward vault is not set
  modifier validateRewardVaultSet() {
    if (address(s_rewardVault) == address(0)) revert RewardVaultNotSet();
    _;
  }

  /// @dev Reverts if pool is after an opening
  modifier whenBeforeOpening() {
    if (s_isOpen) revert PoolHasBeenOpened();
    if (s_pool.state.closedAt != 0) revert PoolHasBeenClosed();
    _;
  }

  /// @dev Reverts if the pool is already closed
  modifier whenBeforeClosing() {
    if (s_pool.state.closedAt != 0) revert PoolHasBeenClosed();
    _;
  }

  /// @dev Reverts if pool is not open
  modifier whenOpen() {
    if (!s_isOpen) revert PoolNotOpen();
    _;
  }

  /// @dev Reverts if pool is not active (is open and rewards are available for this pool)
  modifier whenActive() {
    if (!_isActive()) revert PoolNotActive();
    _;
  }

  /// @dev Reverts if pool is not closed
  modifier whenClosed() {
    if (s_pool.state.closedAt == 0) revert PoolNotClosed();
    _;
  }

  /// @dev Reverts if reward vault is not open or is paused
  modifier whenRewardVaultOpen() {
    if (!s_rewardVault.isOpen() || s_rewardVault.isPaused()) revert RewardVaultNotActive();
    _;
  }

  /// @dev Reverts if reward vault has not had rewards added to it
  modifier whenRewardVaultHasRewards() {
    if (!_hasRewardVaultRewardAdded()) revert RewardVaultHasNoRewards();
    _;
  }
}

// File: lib/openzeppelin-contracts/contracts/utils/Checkpoints.sol



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

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SafeCast.sol";

/**
 * @dev This library defines the `History` struct, for checkpointing values as they change at different points in
 * time, and later looking up past values by block number. See {Votes} as an example.
 *
 * To create a history of checkpoints define a variable type `Checkpoints.History` in your contract, and store a new
 * checkpoint for the current transaction block using the {push} function.
 *
 * _Available since v4.5._
 */
library Checkpoints {
    struct History {
        Checkpoint[] _checkpoints;
    }

    struct Checkpoint {
        uint32 _blockNumber;
        uint224 _value;
    }

    /**
     * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one
     * before it is returned, or zero otherwise. Because the number returned corresponds to that at the end of the
     * block, the requested block number must be in the past, excluding the current block.
     */
    function getAtBlock(History storage self, uint256 blockNumber) internal view returns (uint256) {
        require(blockNumber < block.number, "Checkpoints: block not yet mined");
        uint32 key = SafeCast.toUint32(blockNumber);

        uint256 len = self._checkpoints.length;
        uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one
     * before it is returned, or zero otherwise. Similar to {upperLookup} but optimized for the case when the searched
     * checkpoint is probably "recent", defined as being among the last sqrt(N) checkpoints where N is the number of
     * checkpoints.
     */
    function getAtProbablyRecentBlock(History storage self, uint256 blockNumber) internal view returns (uint256) {
        require(blockNumber < block.number, "Checkpoints: block not yet mined");
        uint32 key = SafeCast.toUint32(blockNumber);

        uint256 len = self._checkpoints.length;

        uint256 low = 0;
        uint256 high = len;

        if (len > 5) {
            uint256 mid = len - Math.sqrt(len);
            if (key < _unsafeAccess(self._checkpoints, mid)._blockNumber) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);

        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block.
     *
     * Returns previous value and new value.
     */
    function push(History storage self, uint256 value) internal returns (uint256, uint256) {
        return _insert(self._checkpoints, SafeCast.toUint32(block.number), SafeCast.toUint224(value));
    }

    /**
     * @dev Pushes a value onto a History, by updating the latest value using binary operation `op`. The new value will
     * be set to `op(latest, delta)`.
     *
     * Returns previous value and new value.
     */
    function push(
        History storage self,
        function(uint256, uint256) view returns (uint256) op,
        uint256 delta
    ) internal returns (uint256, uint256) {
        return push(self, op(latest(self), delta));
    }

    /**
     * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
     */
    function latest(History storage self) internal view returns (uint224) {
        uint256 pos = self._checkpoints.length;
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
     * in the most recent checkpoint.
     */
    function latestCheckpoint(
        History storage self
    ) internal view returns (bool exists, uint32 _blockNumber, uint224 _value) {
        uint256 pos = self._checkpoints.length;
        if (pos == 0) {
            return (false, 0, 0);
        } else {
            Checkpoint memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
            return (true, ckpt._blockNumber, ckpt._value);
        }
    }

    /**
     * @dev Returns the number of checkpoint.
     */
    function length(History storage self) internal view returns (uint256) {
        return self._checkpoints.length;
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
     * or by updating the last one.
     */
    function _insert(Checkpoint[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) {
        uint256 pos = self.length;

        if (pos > 0) {
            // Copying to memory is important here.
            Checkpoint memory last = _unsafeAccess(self, pos - 1);

            // Checkpoint keys must be non-decreasing.
            require(last._blockNumber <= key, "Checkpoint: decreasing keys");

            // Update or push new checkpoint
            if (last._blockNumber == key) {
                _unsafeAccess(self, pos - 1)._value = value;
            } else {
                self.push(Checkpoint({_blockNumber: key, _value: value}));
            }
            return (last._value, value);
        } else {
            self.push(Checkpoint({_blockNumber: key, _value: value}));
            return (0, value);
        }
    }

    /**
     * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` if there is none.
     * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _upperBinaryLookup(
        Checkpoint[] storage self,
        uint32 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._blockNumber > key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return high;
    }

    /**
     * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or `high` if there is none.
     * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _lowerBinaryLookup(
        Checkpoint[] storage self,
        uint32 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._blockNumber < key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return high;
    }

    /**
     * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
     */
    function _unsafeAccess(Checkpoint[] storage self, uint256 pos) private pure returns (Checkpoint storage result) {
        assembly {
            mstore(0, self.slot)
            result.slot := add(keccak256(0, 0x20), pos)
        }
    }

    struct Trace224 {
        Checkpoint224[] _checkpoints;
    }

    struct Checkpoint224 {
        uint32 _key;
        uint224 _value;
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into a Trace224 so that it is stored as the checkpoint.
     *
     * Returns previous value and new value.
     */
    function push(Trace224 storage self, uint32 key, uint224 value) internal returns (uint224, uint224) {
        return _insert(self._checkpoints, key, value);
    }

    /**
     * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if there is none.
     */
    function lowerLookup(Trace224 storage self, uint32 key) internal view returns (uint224) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
        return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none.
     */
    function upperLookup(Trace224 storage self, uint32 key) internal view returns (uint224) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none.
     *
     * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys).
     */
    function upperLookupRecent(Trace224 storage self, uint32 key) internal view returns (uint224) {
        uint256 len = self._checkpoints.length;

        uint256 low = 0;
        uint256 high = len;

        if (len > 5) {
            uint256 mid = len - Math.sqrt(len);
            if (key < _unsafeAccess(self._checkpoints, mid)._key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);

        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
     */
    function latest(Trace224 storage self) internal view returns (uint224) {
        uint256 pos = self._checkpoints.length;
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
     * in the most recent checkpoint.
     */
    function latestCheckpoint(Trace224 storage self) internal view returns (bool exists, uint32 _key, uint224 _value) {
        uint256 pos = self._checkpoints.length;
        if (pos == 0) {
            return (false, 0, 0);
        } else {
            Checkpoint224 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
            return (true, ckpt._key, ckpt._value);
        }
    }

    /**
     * @dev Returns the number of checkpoint.
     */
    function length(Trace224 storage self) internal view returns (uint256) {
        return self._checkpoints.length;
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
     * or by updating the last one.
     */
    function _insert(Checkpoint224[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) {
        uint256 pos = self.length;

        if (pos > 0) {
            // Copying to memory is important here.
            Checkpoint224 memory last = _unsafeAccess(self, pos - 1);

            // Checkpoint keys must be non-decreasing.
            require(last._key <= key, "Checkpoint: decreasing keys");

            // Update or push new checkpoint
            if (last._key == key) {
                _unsafeAccess(self, pos - 1)._value = value;
            } else {
                self.push(Checkpoint224({_key: key, _value: value}));
            }
            return (last._value, value);
        } else {
            self.push(Checkpoint224({_key: key, _value: value}));
            return (0, value);
        }
    }

    /**
     * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` if there is none.
     * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _upperBinaryLookup(
        Checkpoint224[] storage self,
        uint32 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key > key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return high;
    }

    /**
     * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or `high` if there is none.
     * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _lowerBinaryLookup(
        Checkpoint224[] storage self,
        uint32 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key < key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return high;
    }

    /**
     * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
     */
    function _unsafeAccess(
        Checkpoint224[] storage self,
        uint256 pos
    ) private pure returns (Checkpoint224 storage result) {
        assembly {
            mstore(0, self.slot)
            result.slot := add(keccak256(0, 0x20), pos)
        }
    }

    struct Trace160 {
        Checkpoint160[] _checkpoints;
    }

    struct Checkpoint160 {
        uint96 _key;
        uint160 _value;
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into a Trace160 so that it is stored as the checkpoint.
     *
     * Returns previous value and new value.
     */
    function push(Trace160 storage self, uint96 key, uint160 value) internal returns (uint160, uint160) {
        return _insert(self._checkpoints, key, value);
    }

    /**
     * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if there is none.
     */
    function lowerLookup(Trace160 storage self, uint96 key) internal view returns (uint160) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len);
        return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none.
     */
    function upperLookup(Trace160 storage self, uint96 key) internal view returns (uint160) {
        uint256 len = self._checkpoints.length;
        uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none.
     *
     * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys).
     */
    function upperLookupRecent(Trace160 storage self, uint96 key) internal view returns (uint160) {
        uint256 len = self._checkpoints.length;

        uint256 low = 0;
        uint256 high = len;

        if (len > 5) {
            uint256 mid = len - Math.sqrt(len);
            if (key < _unsafeAccess(self._checkpoints, mid)._key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);

        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
     */
    function latest(Trace160 storage self) internal view returns (uint160) {
        uint256 pos = self._checkpoints.length;
        return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
    }

    /**
     * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
     * in the most recent checkpoint.
     */
    function latestCheckpoint(Trace160 storage self) internal view returns (bool exists, uint96 _key, uint160 _value) {
        uint256 pos = self._checkpoints.length;
        if (pos == 0) {
            return (false, 0, 0);
        } else {
            Checkpoint160 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
            return (true, ckpt._key, ckpt._value);
        }
    }

    /**
     * @dev Returns the number of checkpoint.
     */
    function length(Trace160 storage self) internal view returns (uint256) {
        return self._checkpoints.length;
    }

    /**
     * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
     * or by updating the last one.
     */
    function _insert(Checkpoint160[] storage self, uint96 key, uint160 value) private returns (uint160, uint160) {
        uint256 pos = self.length;

        if (pos > 0) {
            // Copying to memory is important here.
            Checkpoint160 memory last = _unsafeAccess(self, pos - 1);

            // Checkpoint keys must be non-decreasing.
            require(last._key <= key, "Checkpoint: decreasing keys");

            // Update or push new checkpoint
            if (last._key == key) {
                _unsafeAccess(self, pos - 1)._value = value;
            } else {
                self.push(Checkpoint160({_key: key, _value: value}));
            }
            return (last._value, value);
        } else {
            self.push(Checkpoint160({_key: key, _value: value}));
            return (0, value);
        }
    }

    /**
     * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` if there is none.
     * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _upperBinaryLookup(
        Checkpoint160[] storage self,
        uint96 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key > key) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return high;
    }

    /**
     * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or `high` if there is none.
     * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`.
     *
     * WARNING: `high` should not be greater than the array's length.
     */
    function _lowerBinaryLookup(
        Checkpoint160[] storage self,
        uint96 key,
        uint256 low,
        uint256 high
    ) private view returns (uint256) {
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (_unsafeAccess(self, mid)._key < key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return high;
    }

    /**
     * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
     */
    function _unsafeAccess(
        Checkpoint160[] storage self,
        uint256 pos
    ) private pure returns (Checkpoint160 storage result) {
        assembly {
            mstore(0, self.slot)
            result.slot := add(keccak256(0, 0x20), pos)
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol



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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

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

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

            return true;
        } else {
            return false;
        }
    }

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

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

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

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

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

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

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

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

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

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

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

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

        return result;
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/math/Math.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

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

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

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

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

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// File: src/interfaces/ISlashable.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface ISlashable {
  /// @notice This error is thrown when the slasher config is invalid
  error InvalidSlasherConfig();

  /// @notice This error is thrown when the contract manager tries to set the slasher role directly
  /// through
  /// `grantRole`
  error InvalidRole();

  /// @notice This error is thrown then the contract manager tries to set the slasher config for an
  /// address
  /// that doesn't have the slasher role
  error InvalidSlasher();

  /// @notice This struct defines the parameters of the slasher config
  struct SlasherConfig {
    /// @notice The pool's refill rate (Juels/sec)
    uint256 refillRate;
    /// @notice The refillable slash capacity amount
    uint256 slashCapacity;
  }

  /// @notice This struct defines the parameters of the slasher state
  struct SlasherState {
    /// @notice The last slash timestamp, will be 0 if never slashed
    /// The timestamp will be set to the time the slashing configuration was configured
    /// instead of 0 if slashing never occurs, refilling slash capacity to full.
    uint256 lastSlashTimestamp;
    /// @notice The current amount of remaining slash capacity
    uint256 remainingSlashCapacityAmount;
  }

  /// @notice This struct defines the slasher's state and config
  struct Slasher {
    /// @notice The slasher's config
    SlasherConfig config;
    /// @notice The slasher's state
    SlasherState state;
  }

  /// @notice Adds a new slasher with the given config
  /// @param slasher The address of the slasher
  /// @param config The slasher config
  function addSlasher(address slasher, SlasherConfig calldata config) external;

  /// @notice Removes a slasher by revoking the SLASHER_ROLE and resetting the slasher config
  /// @param slasher The address of the slasher
  function removeSlasher(address slasher) external;

  /// @notice Sets the slasher config
  /// @param slasher The address of the slasher
  /// @param config The slasher config
  function setSlasherConfig(address slasher, SlasherConfig calldata config) external;

  /// @notice Returns the slasher config
  /// @param slasher The slasher
  /// @return The slasher config
  function getSlasherConfig(address slasher) external view returns (SlasherConfig memory);

  /// @notice Returns the slash capacity for a slasher
  /// @param slasher The slasher
  /// @return The slash capacity
  function getSlashCapacity(address slasher) external view returns (uint256);

  /// @notice Slashes stakers and rewards the alerter.  Moves slashed staker
  /// funds into the alerter reward funds.  The alerter is then
  /// rewarded by the funds in the alerter reward funds.
  /// @param stakers The list of stakers to slash
  /// @param alerter The alerter that successfully raised the alert
  /// @param principalAmount The amount of the staker's staked LINK amount to slash
  /// @param alerterRewardAmount The reward amount to be given to the alerter
  function slashAndReward(
    address[] calldata stakers,
    address alerter,
    uint256 principalAmount,
    uint256 alerterRewardAmount
  ) external;
}

// File: src/interfaces/IRewardVault.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IRewardVault {
  /// @notice This enum describes the different staker types
  enum StakerType {
    NOT_STAKED,
    COMMUNITY,
    OPERATOR
  }

  /// @notice This struct is used to store the reward information for a staker.
  struct StakerReward {
    /// @notice The staker's accrued multiplier-applied reward that's accounted for and stored.
    /// This is used for storing delegated rewards and preserving the staker's past rewards between
    /// unstakes or multiplier resets.
    /// To get the full claimable reward amount, this value is added to the stored reward *
    /// multiplier.
    /// @dev This value is reset when a staker calls claimRewards and rewards
    /// are transferred to the staker.
    uint112 vestedBaseReward;
    /// @notice The staker's accrued delegated reward that's accounted for and stored.
    /// Delegated rewards are not subject to the ramp up multiplier and are immediately finalized.
    /// @dev This value is reset when a staker calls claimRewards and rewards
    /// are transferred to the staker.
    uint112 vestedDelegatedReward;
    /// @notice The last updated per-token base reward of the staker.  This
    /// value only increases over time
    uint112 baseRewardPerToken;
    /// @notice The last updated per-token delegated reward of the operator
    uint112 operatorDelegatedRewardPerToken;
    /// @notice The staker type
    /// @dev This value is set once the first time a staker stakes. This value is used to enforce
    /// that a community staker is not added as an operator.
    StakerType stakerType;
    /// @notice The amount of base rewards that the staker has claimed between
    /// the last time they staked/unstaked until they stake, unstake again or
    /// when an operator is removed.
    /// @dev This is reset to 0 whenever concludeRewardPeriod is called
    /// @dev This is set to vestedBaseReward whenever claimReward is called
    /// @dev Invariant: The sum of unvestedBaseReward and claimedBaseRewardsInPeriod
    /// is the total amount of base rewards a staker has earned since the last time
    /// they stake/unstake.
    uint112 claimedBaseRewardsInPeriod;
    /// @notice The staker's earned but unvested base rewards. The staker's current multiplier is
    /// applied to get the vested base reward amount.
    uint112 unvestedBaseReward;
  }

  /// @notice Claims reward earned by a staker.
  /// @return uint256 The amount of rewards claimed in juels
  function claimReward() external returns (uint256);

  /// @notice Updates the staking pools' reward per token and staker’s reward state
  /// in the reward vault. This is called whenever an operator is slashed as we want
  /// to update the operator's rewards state without resetting their multiplier.
  /// @param staker The staker's address. If this is set to zero address,
  /// staker's reward update will be skipped
  /// @param stakerPrincipal The staker's current staked LINK amount in juels
  function updateReward(address staker, uint256 stakerPrincipal) external;

  /// @notice Concludes the staker's current reward period (defined by a multiplier reset).
  /// This will apply the staker's current ramp up multiplier to their
  /// earned rewards and store the amount of rewards they have earned before
  /// their multiplier is reset.
  /// @dev This is called whenever 1) A staker stakes 2) A staker unstakes
  /// 3) An operator is removed as we want to update the staker's
  /// rewards AND reset their multiplier.
  /// @dev Staker rewards are not forfeited when they stake before they have
  /// reached their maximum ramp up period multiplier.  Instead these
  /// rewards are stored as already earned rewards and will be subject to the
  /// multiplier the next time the contract calculates the staker's claimable
  /// rewards.
  /// @dev Staker rewards are forfeited when a staker unstakes before they
  /// have reached their maximum ramp up period multiplier.  Additionally an
  /// operator will also forfeit any unclaimable rewards if they are removed
  /// before they reach the maximum ramp up period multiplier.  The amount of
  /// rewards forfeited is proportional to the amount unstaked relative to
  /// the staker's total staked LINK amount when unstaking.  A removed operator forfeits
  /// 100% of their unclaimable rewards.
  /// @param staker The staker addres
  /// @param oldPrincipal The staker's staked LINK amount before finalizing
  /// @param stakedAt The last time the staker staked at
  /// @param unstakedAmount The amount that the staker has unstaked in juels
  /// @param shouldForfeit True if rewards should be forfeited
  function concludeRewardPeriod(
    address staker,
    uint256 oldPrincipal,
    uint256 stakedAt,
    uint256 unstakedAmount,
    bool shouldForfeit
  ) external;

  /// @notice Closes the reward vault, disabling adding rewards and staking
  function close() external;

  /// @notice Returns a boolean that is true if the reward vault is open
  /// @return True if open, false otherwise
  function isOpen() external view returns (bool);

  /// @notice Returns the rewards that the staker would get if they withdraw now
  /// Rewards calculation is based on the staker's multiplier
  /// @param staker The staker's address
  /// @return The reward amount
  function getReward(address staker) external view returns (uint256);

  /// @notice Returns the stored reward info of the staker
  /// @param staker The staker address
  /// @return The staker's stored reward info
  function getStoredReward(address staker) external view returns (StakerReward memory);

  /// @notice Returns whether or not the vault is paused
  /// @return bool True if the vault is paused
  function isPaused() external view returns (bool);

  /// @notice Returns whether or not the reward duration for the pool has ended
  /// @param stakingPool The address of the staking pool rewards are being shared to
  /// @return bool True if the reward duration has ended
  function hasRewardDurationEnded(address stakingPool) external view returns (bool);

  /// @notice Returns whether or not the reward vault has had rewards added to it
  /// @return bool True if the reward vault has had rewards added to it
  function hasRewardAdded() external view returns (bool);

  /// @notice Returns the staking pools that are earning rewards from
  /// the reward vault
  /// @return address[] The staking pools that are earning rewards from the
  /// reward vault
  function getStakingPools() external view returns (address[] memory);
}

// File: lib/openzeppelin-contracts/contracts/access/AccessControl.sol



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

pragma solidity ^0.8.0;

import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

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

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `_msgSender()` is missing `role`.
     * Overriding this function changes the behavior of the {onlyRole} modifier.
     *
     * Format of the revert message is described in {_checkRole}.
     *
     * _Available since v4.6._
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        Strings.toHexString(account),
                        " is missing role ",
                        Strings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        return _roles[role].adminRole;
    }

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

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

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * May emit a {RoleGranted} event.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/access/IAccessControlDefaultAdminRules.sol



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

pragma solidity ^0.8.0;

import "./IAccessControl.sol";

/**
 * @dev External interface of AccessControlDefaultAdminRules declared to support ERC165 detection.
 *
 * _Available since v4.9._
 */
interface IAccessControlDefaultAdminRules is IAccessControl {
    /**
     * @dev Emitted when a {defaultAdmin} transfer is started, setting `newAdmin` as the next
     * address to become the {defaultAdmin} by calling {acceptDefaultAdminTransfer} only after `acceptSchedule`
     * passes.
     */
    event DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule);

    /**
     * @dev Emitted when a {pendingDefaultAdmin} is reset if it was never accepted, regardless of its schedule.
     */
    event DefaultAdminTransferCanceled();

    /**
     * @dev Emitted when a {defaultAdminDelay} change is started, setting `newDelay` as the next
     * delay to be applied between default admin transfer after `effectSchedule` has passed.
     */
    event DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule);

    /**
     * @dev Emitted when a {pendingDefaultAdminDelay} is reset if its schedule didn't pass.
     */
    event DefaultAdminDelayChangeCanceled();

    /**
     * @dev Returns the address of the current `DEFAULT_ADMIN_ROLE` holder.
     */
    function defaultAdmin() external view returns (address);

    /**
     * @dev Returns a tuple of a `newAdmin` and an accept schedule.
     *
     * After the `schedule` passes, the `newAdmin` will be able to accept the {defaultAdmin} role
     * by calling {acceptDefaultAdminTransfer}, completing the role transfer.
     *
     * A zero value only in `acceptSchedule` indicates no pending admin transfer.
     *
     * NOTE: A zero address `newAdmin` means that {defaultAdmin} is being renounced.
     */
    function pendingDefaultAdmin() external view returns (address newAdmin, uint48 acceptSchedule);

    /**
     * @dev Returns the delay required to schedule the acceptance of a {defaultAdmin} transfer started.
     *
     * This delay will be added to the current timestamp when calling {beginDefaultAdminTransfer} to set
     * the acceptance schedule.
     *
     * NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this
     * function returns the new delay. See {changeDefaultAdminDelay}.
     */
    function defaultAdminDelay() external view returns (uint48);

    /**
     * @dev Returns a tuple of `newDelay` and an effect schedule.
     *
     * After the `schedule` passes, the `newDelay` will get into effect immediately for every
     * new {defaultAdmin} transfer started with {beginDefaultAdminTransfer}.
     *
     * A zero value only in `effectSchedule` indicates no pending delay change.
     *
     * NOTE: A zero value only for `newDelay` means that the next {defaultAdminDelay}
     * will be zero after the effect schedule.
     */
    function pendingDefaultAdminDelay() external view returns (uint48 newDelay, uint48 effectSchedule);

    /**
     * @dev Starts a {defaultAdmin} transfer by setting a {pendingDefaultAdmin} scheduled for acceptance
     * after the current timestamp plus a {defaultAdminDelay}.
     *
     * Requirements:
     *
     * - Only can be called by the current {defaultAdmin}.
     *
     * Emits a DefaultAdminRoleChangeStarted event.
     */
    function beginDefaultAdminTransfer(address newAdmin) external;

    /**
     * @dev Cancels a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}.
     *
     * A {pendingDefaultAdmin} not yet accepted can also be cancelled with this function.
     *
     * Requirements:
     *
     * - Only can be called by the current {defaultAdmin}.
     *
     * May emit a DefaultAdminTransferCanceled event.
     */
    function cancelDefaultAdminTransfer() external;

    /**
     * @dev Completes a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}.
     *
     * After calling the function:
     *
     * - `DEFAULT_ADMIN_ROLE` should be granted to the caller.
     * - `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder.
     * - {pendingDefaultAdmin} should be reset to zero values.
     *
     * Requirements:
     *
     * - Only can be called by the {pendingDefaultAdmin}'s `newAdmin`.
     * - The {pendingDefaultAdmin}'s `acceptSchedule` should've passed.
     */
    function acceptDefaultAdminTransfer() external;

    /**
     * @dev Initiates a {defaultAdminDelay} update by setting a {pendingDefaultAdminDelay} scheduled for getting
     * into effect after the current timestamp plus a {defaultAdminDelay}.
     *
     * This function guarantees that any call to {beginDefaultAdminTransfer} done between the timestamp this
     * method is called and the {pendingDefaultAdminDelay} effect schedule will use the current {defaultAdminDelay}
     * set before calling.
     *
     * The {pendingDefaultAdminDelay}'s effect schedule is defined in a way that waiting until the schedule and then
     * calling {beginDefaultAdminTransfer} with the new delay will take at least the same as another {defaultAdmin}
     * complete transfer (including acceptance).
     *
     * The schedule is designed for two scenarios:
     *
     * - When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by
     * {defaultAdminDelayIncreaseWait}.
     * - When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`.
     *
     * A {pendingDefaultAdminDelay} that never got into effect will be canceled in favor of a new scheduled change.
     *
     * Requirements:
     *
     * - Only can be called by the current {defaultAdmin}.
     *
     * Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event.
     */
    function changeDefaultAdminDelay(uint48 newDelay) external;

    /**
     * @dev Cancels a scheduled {defaultAdminDelay} change.
     *
     * Requirements:
     *
     * - Only can be called by the current {defaultAdmin}.
     *
     * May emit a DefaultAdminDelayChangeCanceled event.
     */
    function rollbackDefaultAdminDelay() external;

    /**
     * @dev Maximum time in seconds for an increase to {defaultAdminDelay} (that is scheduled using {changeDefaultAdminDelay})
     * to take effect. Default to 5 days.
     *
     * When the {defaultAdminDelay} is scheduled to be increased, it goes into effect after the new delay has passed with
     * the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds)
     * that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can
     * be overrode for a custom {defaultAdminDelay} increase scheduling.
     *
     * IMPORTANT: Make sure to add a reasonable amount of time while overriding this value, otherwise,
     * there's a risk of setting a high new delay that goes into effect almost immediately without the
     * possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds).
     */
    function defaultAdminDelayIncreaseWait() external view returns (uint48);
}

// File: lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol



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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     *
     * _Available since v4.7._
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     *
     * _Available since v4.7._
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     *
     * _Available since v4.7._
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     *
     * _Available since v4.7._
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     *
     * _Available since v4.7._
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     *
     * _Available since v4.7._
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     *
     * _Available since v4.7._
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     *
     * _Available since v4.7._
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     *
     * _Available since v4.7._
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     *
     * _Available since v4.7._
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     *
     * _Available since v4.7._
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     *
     * _Available since v4.7._
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     *
     * _Available since v4.7._
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     *
     * _Available since v4.7._
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     *
     * _Available since v4.7._
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
    }

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

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     *
     * _Available since v4.7._
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     *
     * _Available since v4.7._
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     *
     * _Available since v4.7._
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     *
     * _Available since v4.7._
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     *
     * _Available since v4.7._
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     *
     * _Available since v4.7._
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     *
     * _Available since v4.7._
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
    }

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

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     *
     * _Available since v4.7._
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     *
     * _Available since v4.7._
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     *
     * _Available since v4.7._
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
    }

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

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     *
     * _Available since v4.7._
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
    }

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

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

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

// File: lib/openzeppelin-contracts/contracts/interfaces/IERC5313.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Interface for the Light Contract Ownership Standard.
 *
 * A standardized minimal interface required to identify an account that controls a contract
 *
 * _Available since v4.9._
 */
interface IERC5313 {
    /**
     * @dev Gets the address of the owner.
     */
    function owner() external view returns (address);
}

// File: lib/openzeppelin-contracts/contracts/utils/Context.sol



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

pragma solidity ^0.8.0;

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

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

// File: lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol



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

pragma solidity ^0.8.0;

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

// File: src/interfaces/IMigratable.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IMigratable {
  /// @notice This error is thrown when the owner tries to set the migration target to the
  /// zero address or an invalid address as well as when the migration target is not set and owner
  /// tries to migrate the contract.
  error InvalidMigrationTarget();

  /// @notice This event is emitted when the migration target is set
  /// @param oldMigrationTarget The previous migration target
  /// @param newMigrationTarget The updated migration target
  event MigrationTargetSet(address indexed oldMigrationTarget, address indexed newMigrationTarget);

  /// @notice Sets the address this contract will be upgraded to
  /// @param newMigrationTarget The address of the migration target
  function setMigrationTarget(address newMigrationTarget) external;

  /// @notice Returns the current migration target of the contract
  /// @return address The current migration target
  function getMigrationTarget() external view returns (address);

  /// @notice Migrates the contract
  /// @param data Optional calldata to call on new contract
  function migrate(bytes calldata data) external;
}

// File: src/interfaces/IStakingOwner.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IStakingOwner {
  /// @notice This event is emitted when the staking pool is opened for staking
  event PoolOpened();
  /// @notice This event is emitted when the staking pool is closed
  event PoolClosed();

  /// @notice This error is thrown when an invalid min operator stake amount is
  /// supplied
  error InvalidMinStakeAmount();
  /// @notice This error is raised when attempting to decrease maximum pool size
  /// @param maxPoolSize the proposed maximum pool size
  error InvalidPoolSize(uint256 maxPoolSize);
  /// @notice This error is raised when attempting to decrease maximum stake amount
  /// for the pool members
  /// @param maxStakeAmount the proposed maximum stake amount
  error InvalidMaxStakeAmount(uint256 maxStakeAmount);

  /// @notice This error is thrown when the staking pool is closed.
  error PoolNotOpen();

  /// @notice This error is thrown when the staking pool is open.
  error PoolNotClosed();

  /// @notice This error is thrown when the staking pool has been opened and contract manager tries
  /// to re-open.
  error PoolHasBeenOpened();

  /// @notice This error is thrown when the pool has been closed and contract manager tries to
  /// re-open
  error PoolHasBeenClosed();

  /// @notice Set the pool config
  /// @param maxPoolSize The max amount of staked LINK allowed in the pool
  /// @param maxPrincipalPerStaker The max amount of LINK a staker can stake
  /// in the pool.
  function setPoolConfig(uint256 maxPoolSize, uint256 maxPrincipalPerStaker) external;

  /// @notice Opens the pool for staking
  function open() external;

  /// @notice Closes the pool
  function close() external;

  /// @notice Sets the migration proxy contract address
  /// @param migrationProxy The migration proxy contract address
  function setMigrationProxy(address migrationProxy) external;
}

// File: src/interfaces/IStakingPool.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {IRewardVault} from "./IRewardVault.sol";
import {Checkpoints} from "@openzeppelin/contracts/utils/Checkpoints.sol";

interface IStakingPool {
  /// @notice This error is thrown when a caller tries to execute a transaction
  /// that they do not have permissions for
  error AccessForbidden();

  /// @notice This event is emitted when the migration proxy address has been set
  /// @param oldMigrationProxy The old migration proxy contract address
  /// @param newMigrationProxy The new migration proxy contract address
  event MigrationProxySet(address indexed oldMigrationProxy, address indexed newMigrationProxy);

  /// @notice This event is emitted when the staking pool's maximum size is
  /// increased
  /// @param maxPoolSize the new maximum pool size
  event PoolSizeIncreased(uint256 maxPoolSize);

  /// @notice This event is emitted when the maximum stake amount
  // for the stakers in the pool is increased
  /// @param maxPrincipalPerStaker the new maximum stake amount
  event MaxPrincipalAmountIncreased(uint256 maxPrincipalPerStaker);

  /// @notice This event is emitted when a staker adds stake to the pool.
  /// @param staker Staker address
  /// @param amount Amount of stake added
  /// @param newStake New LINK amount staked
  /// @param newTotalPrincipal Total amount of juels staked in the pool
  event Staked(address indexed staker, uint256 amount, uint256 newStake, uint256 newTotalPrincipal);

  /// @notice This event is emitted when a staker removes stake from the pool.
  /// @param staker Staker address
  /// @param amount Amount of stake removed
  /// @param newStake New LINK amount staked
  /// @param newTotalPrincipal Total amount of staked juels remaining in the pool
  event Unstaked(
    address indexed staker, uint256 amount, uint256 newStake, uint256 newTotalPrincipal
  );

  /// @notice This error is thrown whenever a zero-address is supplied when
  /// a non-zero address is required
  error InvalidZeroAddress();

  /// @notice This error is thrown whenever the sender is not the LINK token
  error SenderNotLinkToken();

  /// @notice This error is thrown whenever the migration proxy address has not been set
  error MigrationProxyNotSet();

  /// @notice This error is thrown whenever the reward vault address has not been set
  error RewardVaultNotSet();

  /// @notice This error is thrown when invalid data is passed to the onTokenTransfer function
  error InvalidData();

  /// @notice This error is thrown when the staker tries to stake less than the min amount
  error InsufficientStakeAmount();

  /// @notice This error is thrown when the staker tries to stake more than the max amount
  error ExceedsMaxStakeAmount();

  /// @notice This error is thrown when the staker tries to stake more than the max pool size
  error ExceedsMaxPoolSize();

  /// @notice This error is raised when stakers attempt to exit the pool
  /// @param staker address of the staker
  error StakeNotFound(address staker);

  /// @notice This error is thrown when the staker tries to unstake a zero amount
  error UnstakeZeroAmount();

  /// @notice This error is thrown when the staker tries to unstake more than the
  /// staked LINK amount
  error UnstakeExceedsPrincipal();

  /// @notice This error is thrown when the staker tries to unstake an amount that leaves their
  /// staked LINK amount below the minimum amount
  error UnstakePrincipalBelowMinAmount();

  /// @notice This struct defines the state of a staker
  struct Staker {
    /// @notice The combined staked LINK amount and staked at time history
    /// @dev Both the staker staked LINK amount and staked at timestamp are stored in uint112 to
    /// save space
    /// @dev The max value of uint112 is greater than the total supply of LINK
    /// @dev The max value of uint112 can represent a timestamp in the year 3615, long after the
    /// staking program has ended
    /// @dev The combination is performed as such:
    /// uint224 history = (uint224(uint112(principal)) << 112) |
    /// uint224(uint112(stakedAtTime))
    Checkpoints.History history;
    /// @notice The staker's unbonding period end time
    uint128 unbondingPeriodEndsAt;
    /// @notice The staker's claim period end time
    uint128 claimPeriodEndsAt;
  }

  /// @notice Unstakes amount LINK tokens from the staker’s staked LINK amount
  /// @param amount The amount of LINK tokens to unstake
  function unstake(uint256 amount) external;

  /// @notice Returns the total amount staked in the pool
  /// @return The total amount staked in pool
  function getTotalPrincipal() external view returns (uint256);

  /// @notice Returns the staker's staked LINK amount
  /// @param staker The address of the staker to query for
  /// @return uint256 The staker's staked LINK amount
  function getStakerPrincipal(address staker) external view returns (uint256);

  /// @notice Returns the staker's staked LINK amount
  /// @param staker The address of the staker to query for
  /// @param blockNumber The block number to fetch the staker's balance for.  Pass 0
  /// to return the staker's latest staked LINK amount
  /// @return uint256 The staker's staked LINK amount
  function getStakerPrincipalAt(
    address staker,
    uint256 blockNumber
  ) external view returns (uint256);

  /// @notice Returns the staker's average staked at time
  /// @param staker The address of the staker to query for
  /// @return uint256 The staker's average staked at time
  function getStakerStakedAtTime(address staker) external view returns (uint256);

  /// @notice Returns the staker's last staked at time for a block number ID
  /// @param staker The address of the staker to query for
  /// @param blockNumber The block number to query for
  /// @return uint256 The staker's staked at time for the block number ID
  function getStakerStakedAtTimeAt(
    address staker,
    uint256 blockNumber
  ) external view returns (uint256);

  /// @notice Returns the current reward vault address
  /// @return The reward vault
  function getRewardVault() external view returns (IRewardVault);

  /// @notice Returns the address of the LINK token contract
  /// @return The LINK token contract's address that is used by the pool
  function getChainlinkToken() external view returns (address);

  /// @notice Returns the migration proxy contract address
  /// @return The migration proxy contract address
  function getMigrationProxy() external view returns (address);

  /// @notice Returns a boolean that is true if the pool is open
  /// @return True if the pool is open, false otherwise
  function isOpen() external view returns (bool);

  /// @notice Returns a boolean that is true if the pool is active,
  /// i.e. is open and there are remaining rewards to vest in the pool.
  /// @return True if the pool is active, false otherwise
  function isActive() external view returns (bool);

  /// @notice Returns the minimum and maximum amounts a staker can stake in the
  /// pool
  /// @return uint256 minimum amount that can be staked by a staker
  /// @return uint256 maximum amount that can be staked by a staker
  function getStakerLimits() external view returns (uint256, uint256);

  /// @notice uint256 Returns the maximum amount that can be staked in the pool
  /// @return uint256 current maximum staking pool size
  function getMaxPoolSize() external view returns (uint256);
}

// File: src/Migratable.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {AccessControlDefaultAdminRules} from
  "@openzeppelin/contracts/access/AccessControlDefaultAdminRules.sol";

import {IMigratable} from "./interfaces/IMigratable.sol";

/// @notice Base contract that adds migration functionality.
abstract contract Migratable is IMigratable, AccessControlDefaultAdminRules {
  /// @notice The address of the new contract that this contract will be upgraded to.
  address internal s_migrationTarget;

  function setMigrationTarget(address newMigrationTarget)
    external
    override
    onlyRole(DEFAULT_ADMIN_ROLE)
  {
    _validateMigrationTarget(newMigrationTarget);

    address oldMigrationTarget = s_migrationTarget;
    s_migrationTarget = newMigrationTarget;

    emit MigrationTargetSet(oldMigrationTarget, newMigrationTarget);
  }

  /// @inheritdoc IMigratable
  function getMigrationTarget() external view returns (address) {
    return s_migrationTarget;
  }

  /// @notice Helper function for validating the migration target
  /// @param newMigrationTarget The address of the new migration target
  function _validateMigrationTarget(address newMigrationTarget) internal virtual {
    if (
      newMigrationTarget == address(0) || newMigrationTarget == address(this)
        || newMigrationTarget == s_migrationTarget || newMigrationTarget.code.length == 0
    ) {
      revert InvalidMigrationTarget();
    }
  }

  /// @dev Reverts if the migration target is not set
  modifier validateMigrationTargetSet() {
    if (s_migrationTarget == address(0)) {
      revert InvalidMigrationTarget();
    }
    _;
  }
}

// File: lib/openzeppelin-contracts/contracts/access/IAccessControl.sol



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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

// File: lib/openzeppelin-contracts/contracts/utils/Strings.sol



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

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

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

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol



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

pragma solidity ^0.8.0;

import "./IERC165.sol";

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

// File: lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol



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

pragma solidity ^0.8.0;

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

// File: lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol



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

pragma solidity ^0.8.0;

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

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

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}