Scroll Sepolia Testnet

Contract

0xB0e5bc69065eF1078fd641aE6A0860441e9e21e4
Source Code Source Code

Overview

ETH Balance

Scroll Sepolia LogoScroll Sepolia LogoScroll Sepolia Logo0 ETH

More Info

Multichain Info

N/A
Transaction Hash
Method
Block
From
To
Amount

There are no matching entries

> 10 Internal Transactions found.

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To Amount
92764112025-04-18 12:22:16288 days ago1744978936
0xB0e5bc69...41e9e21e4
0 ETH
92764112025-04-18 12:22:16288 days ago1744978936
0xB0e5bc69...41e9e21e4
0 ETH
92764032025-04-18 12:21:32288 days ago1744978892
0xB0e5bc69...41e9e21e4
0 ETH
92764032025-04-18 12:21:32288 days ago1744978892
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
84661812025-03-11 11:53:54326 days ago1741694034
0xB0e5bc69...41e9e21e4
0 ETH
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
TwabController

Compiler Version
v0.8.24+commit.e11b9ed9

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 5 : TwabController.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { SafeCast } from "openzeppelin/utils/math/SafeCast.sol";
import { TwabLib } from "./libraries/TwabLib.sol";
import { ObservationLib } from "./libraries/ObservationLib.sol";

/// @notice Emitted when an account already points to the same delegate address that is being set
error SameDelegateAlreadySet(address delegate);

/// @notice Emitted when an account tries to transfer to the sponsorship address
error CannotTransferToSponsorshipAddress();

/// @notice Emitted when the period length is too short
error PeriodLengthTooShort();

/// @notice Emitted when the period offset is not in the past.
/// @param periodOffset The period offset that was passed in
error PeriodOffsetInFuture(uint32 periodOffset);

/// @notice Emitted when a user tries to mint or transfer to the zero address
error TransferToZeroAddress();

// The minimum period length
uint32 constant MINIMUM_PERIOD_LENGTH = 1 hours;

// Allows users to revoke their chances to win by delegating to the sponsorship address.
address constant SPONSORSHIP_ADDRESS = address(1);

/**
 * @title  PoolTogether V5 Time-Weighted Average Balance Controller
 * @author PoolTogether Inc. & G9 Software Inc.
 * @dev    Time-Weighted Average Balance Controller for ERC20 tokens.
 * @notice This TwabController uses the TwabLib to provide token balances and on-chain historical
            lookups to a user(s) time-weighted average balance. Each user is mapped to an
            Account struct containing the TWAB history (ring buffer) and ring buffer parameters.
            Every token.transfer() creates a new TWAB observation. The new TWAB observation is
            stored in the circular ring buffer as either a new observation or rewriting a
            previous observation with new parameters. One observation per period is stored.
            The TwabLib guarantees minimum 1 year of search history if a period is a day.
 */
contract TwabController {
  using SafeCast for uint256;

  /// @notice Sets the minimum period length for Observations. When a period elapses, a new Observation is recorded, otherwise the most recent Observation is updated.
  uint32 public immutable PERIOD_LENGTH;

  /// @notice Sets the beginning timestamp for the first period. This allows us to maximize storage as well as line up periods with a chosen timestamp.
  /// @dev Ensure that the PERIOD_OFFSET is in the past.
  uint32 public immutable PERIOD_OFFSET;

  /* ============ State ============ */

  /// @notice Record of token holders TWABs for each account for each vault.
  mapping(address => mapping(address => TwabLib.Account)) internal userObservations;

  /// @notice Record of tickets total supply and ring buff parameters used for observation.
  mapping(address => TwabLib.Account) internal totalSupplyObservations;

  /// @notice vault => user => delegate.
  mapping(address => mapping(address => address)) internal delegates;

  /* ============ Events ============ */

  /**
   * @notice Emitted when a balance or delegateBalance is increased.
   * @param vault the vault for which the balance increased
   * @param user the users whose balance increased
   * @param amount the amount the balance increased by
   * @param delegateAmount the amount the delegateBalance increased by
   */
  event IncreasedBalance(
    address indexed vault,
    address indexed user,
    uint96 amount,
    uint96 delegateAmount
  );

  /**
   * @notice Emitted when a balance or delegateBalance is decreased.
   * @param vault the vault for which the balance decreased
   * @param user the users whose balance decreased
   * @param amount the amount the balance decreased by
   * @param delegateAmount the amount the delegateBalance decreased by
   */
  event DecreasedBalance(
    address indexed vault,
    address indexed user,
    uint96 amount,
    uint96 delegateAmount
  );

  /**
   * @notice Emitted when an Observation is recorded to the Ring Buffer.
   * @param vault the vault for which the Observation was recorded
   * @param user the users whose Observation was recorded
   * @param balance the resulting balance
   * @param delegateBalance the resulting delegated balance
   * @param isNew whether the observation is new or not
   * @param observation the observation that was created or updated
   */
  event ObservationRecorded(
    address indexed vault,
    address indexed user,
    uint96 balance,
    uint96 delegateBalance,
    bool isNew,
    ObservationLib.Observation observation
  );

  /**
   * @notice Emitted when a user delegates their balance to another address.
   * @param vault the vault for which the balance was delegated
   * @param delegator the user who delegated their balance
   * @param delegate the user who received the delegated balance
   */
  event Delegated(address indexed vault, address indexed delegator, address indexed delegate);

  /**
   * @notice Emitted when the total supply or delegateTotalSupply is increased.
   * @param vault the vault for which the total supply increased
   * @param amount the amount the total supply increased by
   * @param delegateAmount the amount the delegateTotalSupply increased by
   */
  event IncreasedTotalSupply(address indexed vault, uint96 amount, uint96 delegateAmount);

  /**
   * @notice Emitted when the total supply or delegateTotalSupply is decreased.
   * @param vault the vault for which the total supply decreased
   * @param amount the amount the total supply decreased by
   * @param delegateAmount the amount the delegateTotalSupply decreased by
   */
  event DecreasedTotalSupply(address indexed vault, uint96 amount, uint96 delegateAmount);

  /**
   * @notice Emitted when a Total Supply Observation is recorded to the Ring Buffer.
   * @param vault the vault for which the Observation was recorded
   * @param balance the resulting balance
   * @param delegateBalance the resulting delegated balance
   * @param isNew whether the observation is new or not
   * @param observation the observation that was created or updated
   */
  event TotalSupplyObservationRecorded(
    address indexed vault,
    uint96 balance,
    uint96 delegateBalance,
    bool isNew,
    ObservationLib.Observation observation
  );

  /* ============ Constructor ============ */

  /**
   * @notice Construct a new TwabController.
   * @dev Reverts if the period offset is in the future.
   * @param _periodLength Sets the minimum period length for Observations. When a period elapses, a new Observation
   *      is recorded, otherwise the most recent Observation is updated.
   * @param _periodOffset Sets the beginning timestamp for the first period. This allows us to maximize storage as well
   *      as line up periods with a chosen timestamp.
   */
  constructor(uint32 _periodLength, uint32 _periodOffset) {
    if (_periodLength < MINIMUM_PERIOD_LENGTH) {
      revert PeriodLengthTooShort();
    }
    if (_periodOffset > block.timestamp) {
      revert PeriodOffsetInFuture(_periodOffset);
    }
    PERIOD_LENGTH = _periodLength;
    PERIOD_OFFSET = _periodOffset;
  }

  /* ============ External Read Functions ============ */

  /**
   * @notice Returns whether the TwabController has been shutdown at the given timestamp
   * If the twab is queried at or after this time, whether an absolute timestamp or time range, it will return 0.
   * @dev This function will return true for any timestamp after the lastObservationAt()
   * @param timestamp The timestamp to check
   * @return True if the TwabController is shutdown at the given timestamp, false otherwise.
   */
  function isShutdownAt(uint256 timestamp) external view returns (bool) {
    return TwabLib.isShutdownAt(timestamp, PERIOD_LENGTH, PERIOD_OFFSET);
  }

  /**
   * @notice Computes the timestamp after which no more observations will be made.
   * @return The largest timestamp at which the TwabController can record a new observation.
   */
  function lastObservationAt() external view returns (uint256) {
    return TwabLib.lastObservationAt(PERIOD_LENGTH, PERIOD_OFFSET);
  }

  /**
   * @notice Loads the current TWAB Account data for a specific vault stored for a user.
   * @dev Note this is a very expensive function
   * @param vault the vault for which the data is being queried
   * @param user the user whose data is being queried
   * @return The current TWAB Account data of the user
   */
  function getAccount(address vault, address user) external view returns (TwabLib.Account memory) {
    return userObservations[vault][user];
  }

  /**
   * @notice Loads the current total supply TWAB Account data for a specific vault.
   * @dev Note this is a very expensive function
   * @param vault the vault for which the data is being queried
   * @return The current total supply TWAB Account data
   */
  function getTotalSupplyAccount(address vault) external view returns (TwabLib.Account memory) {
    return totalSupplyObservations[vault];
  }

  /**
   * @notice The current token balance of a user for a specific vault.
   * @param vault the vault for which the balance is being queried
   * @param user the user whose balance is being queried
   * @return The current token balance of the user
   */
  function balanceOf(address vault, address user) external view returns (uint256) {
    return userObservations[vault][user].details.balance;
  }

  /**
   * @notice The total supply of tokens for a vault.
   * @param vault the vault for which the total supply is being queried
   * @return The total supply of tokens for a vault
   */
  function totalSupply(address vault) external view returns (uint256) {
    return totalSupplyObservations[vault].details.balance;
  }

  /**
   * @notice The total delegated amount of tokens for a vault.
   * @dev Delegated balance is not 1:1 with the token total supply. Users may delegate their
   *      balance to the sponsorship address, which will result in those tokens being subtracted
   *      from the total.
   * @param vault the vault for which the total delegated supply is being queried
   * @return The total delegated amount of tokens for a vault
   */
  function totalSupplyDelegateBalance(address vault) external view returns (uint256) {
    return totalSupplyObservations[vault].details.delegateBalance;
  }

  /**
   * @notice The current delegate of a user for a specific vault.
   * @param vault the vault for which the delegate balance is being queried
   * @param user the user whose delegate balance is being queried
   * @return The current delegate balance of the user
   */
  function delegateOf(address vault, address user) external view returns (address) {
    return _delegateOf(vault, user);
  }

  /**
   * @notice The current delegateBalance of a user for a specific vault.
   * @dev the delegateBalance is the sum of delegated balance to this user
   * @param vault the vault for which the delegateBalance is being queried
   * @param user the user whose delegateBalance is being queried
   * @return The current delegateBalance of the user
   */
  function delegateBalanceOf(address vault, address user) external view returns (uint256) {
    return userObservations[vault][user].details.delegateBalance;
  }

  /**
   * @notice Looks up a users balance at a specific time in the past.
   * @param vault the vault for which the balance is being queried
   * @param user the user whose balance is being queried
   * @param periodEndOnOrAfterTime The time in the past for which the balance is being queried. The time will be snapped to a period end time on or after the timestamp.
   * @return The balance of the user at the target time
   */
  function getBalanceAt(
    address vault,
    address user,
    uint256 periodEndOnOrAfterTime
  ) external view returns (uint256) {
    TwabLib.Account storage _account = userObservations[vault][user];
    return
      TwabLib.getBalanceAt(
        PERIOD_LENGTH,
        PERIOD_OFFSET,
        _account.observations,
        _account.details,
        _periodEndOnOrAfter(periodEndOnOrAfterTime)
      );
  }

  /**
   * @notice Looks up the total supply at a specific time in the past.
   * @param vault the vault for which the total supply is being queried
   * @param periodEndOnOrAfterTime The time in the past for which the balance is being queried. The time will be snapped to a period end time on or after the timestamp.
   * @return The total supply at the target time
   */
  function getTotalSupplyAt(
    address vault,
    uint256 periodEndOnOrAfterTime
  ) external view returns (uint256) {
    TwabLib.Account storage _account = totalSupplyObservations[vault];
    return
      TwabLib.getBalanceAt(
        PERIOD_LENGTH,
        PERIOD_OFFSET,
        _account.observations,
        _account.details,
        _periodEndOnOrAfter(periodEndOnOrAfterTime)
      );
  }

  /**
   * @notice Looks up the average balance of a user between two timestamps.
   * @dev Timestamps are Unix timestamps denominated in seconds
   * @param vault the vault for which the average balance is being queried
   * @param user the user whose average balance is being queried
   * @param startTime the start of the time range for which the average balance is being queried. The time will be snapped to a period end time on or after the timestamp.
   * @param endTime the end of the time range for which the average balance is being queried. The time will be snapped to a period end time on or after the timestamp.
   * @return The average balance of the user between the two timestamps
   */
  function getTwabBetween(
    address vault,
    address user,
    uint256 startTime,
    uint256 endTime
  ) external view returns (uint256) {
    TwabLib.Account storage _account = userObservations[vault][user];
    // We snap the timestamps to the period end on or after the timestamp because the total supply records will be sparsely populated.
    // if two users update during a period, then the total supply observation will only exist for the last one.
    return
      TwabLib.getTwabBetween(
        PERIOD_LENGTH,
        PERIOD_OFFSET,
        _account.observations,
        _account.details,
        _periodEndOnOrAfter(startTime),
        _periodEndOnOrAfter(endTime)
      );
  }

  /**
   * @notice Looks up the average total supply between two timestamps.
   * @dev Timestamps are Unix timestamps denominated in seconds
   * @param vault the vault for which the average total supply is being queried
   * @param startTime the start of the time range for which the average total supply is being queried
   * @param endTime the end of the time range for which the average total supply is being queried
   * @return The average total supply between the two timestamps
   */
  function getTotalSupplyTwabBetween(
    address vault,
    uint256 startTime,
    uint256 endTime
  ) external view returns (uint256) {
    TwabLib.Account storage _account = totalSupplyObservations[vault];
    // We snap the timestamps to the period end on or after the timestamp because the total supply records will be sparsely populated.
    // if two users update during a period, then the total supply observation will only exist for the last one.
    return
      TwabLib.getTwabBetween(
        PERIOD_LENGTH,
        PERIOD_OFFSET,
        _account.observations,
        _account.details,
        _periodEndOnOrAfter(startTime),
        _periodEndOnOrAfter(endTime)
      );
  }

  /**
   * @notice Computes the period end timestamp on or after the given timestamp.
   * @param _timestamp The timestamp to check
   * @return The end timestamp of the period that ends on or immediately after the given timestamp
   */
  function periodEndOnOrAfter(uint256 _timestamp) external view returns (uint256) {
    return _periodEndOnOrAfter(_timestamp);
  }

  /**
   * @notice Computes the period end timestamp on or after the given timestamp.
   * @param _timestamp The timestamp to compute the period end time for
   * @return A period end time.
   */
  function _periodEndOnOrAfter(uint256 _timestamp) internal view returns (uint256) {
    if (_timestamp < PERIOD_OFFSET) {
      return PERIOD_OFFSET;
    }
    if ((_timestamp - PERIOD_OFFSET) % PERIOD_LENGTH == 0) {
      return _timestamp;
    }
    uint256 period = TwabLib.getTimestampPeriod(PERIOD_LENGTH, PERIOD_OFFSET, _timestamp);
    return TwabLib.getPeriodEndTime(PERIOD_LENGTH, PERIOD_OFFSET, period);
  }

  /**
   * @notice Looks up the newest observation for a user.
   * @param vault the vault for which the observation is being queried
   * @param user the user whose observation is being queried
   * @return index The index of the observation
   * @return observation The observation of the user
   */
  function getNewestObservation(
    address vault,
    address user
  ) external view returns (uint16, ObservationLib.Observation memory) {
    TwabLib.Account storage _account = userObservations[vault][user];
    return TwabLib.getNewestObservation(_account.observations, _account.details);
  }

  /**
   * @notice Looks up the oldest observation for a user.
   * @param vault the vault for which the observation is being queried
   * @param user the user whose observation is being queried
   * @return index The index of the observation
   * @return observation The observation of the user
   */
  function getOldestObservation(
    address vault,
    address user
  ) external view returns (uint16, ObservationLib.Observation memory) {
    TwabLib.Account storage _account = userObservations[vault][user];
    return TwabLib.getOldestObservation(_account.observations, _account.details);
  }

  /**
   * @notice Looks up the newest total supply observation for a vault.
   * @param vault the vault for which the observation is being queried
   * @return index The index of the observation
   * @return observation The total supply observation
   */
  function getNewestTotalSupplyObservation(
    address vault
  ) external view returns (uint16, ObservationLib.Observation memory) {
    TwabLib.Account storage _account = totalSupplyObservations[vault];
    return TwabLib.getNewestObservation(_account.observations, _account.details);
  }

  /**
   * @notice Looks up the oldest total supply observation for a vault.
   * @param vault the vault for which the observation is being queried
   * @return index The index of the observation
   * @return observation The total supply observation
   */
  function getOldestTotalSupplyObservation(
    address vault
  ) external view returns (uint16, ObservationLib.Observation memory) {
    TwabLib.Account storage _account = totalSupplyObservations[vault];
    return TwabLib.getOldestObservation(_account.observations, _account.details);
  }

  /**
   * @notice Calculates the period a timestamp falls into.
   * @param time The timestamp to check
   * @return period The period the timestamp falls into
   */
  function getTimestampPeriod(uint256 time) external view returns (uint256) {
    return TwabLib.getTimestampPeriod(PERIOD_LENGTH, PERIOD_OFFSET, time);
  }

  /**
   * @notice Checks if the given timestamp is before the current overwrite period.
   * @param time The timestamp to check
   * @return True if the given time is finalized, false if it's during the current overwrite period.
   */
  function hasFinalized(uint256 time) external view returns (bool) {
    return TwabLib.hasFinalized(PERIOD_LENGTH, PERIOD_OFFSET, time);
  }

  /**
   * @notice Computes the timestamp at which the current overwrite period started.
   * @dev The overwrite period is the period during which observations are collated.
   * @return period The timestamp at which the current overwrite period started.
   */
  function currentOverwritePeriodStartedAt() external view returns (uint256) {
    return TwabLib.currentOverwritePeriodStartedAt(PERIOD_LENGTH, PERIOD_OFFSET);
  }

  /* ============ External Write Functions ============ */

  /**
   * @notice Mints new balance and delegateBalance for a given user.
   * @dev Note that if the provided user to mint to is delegating that the delegate's
   *      delegateBalance will be updated.
   * @dev Mint is expected to be called by the Vault.
   * @param _to The address to mint balance and delegateBalance to
   * @param _amount The amount to mint
   */
  function mint(address _to, uint96 _amount) external {
    if (_to == address(0)) {
      revert TransferToZeroAddress();
    }
    _transferBalance(msg.sender, address(0), _to, _amount);
  }

  /**
   * @notice Burns balance and delegateBalance for a given user.
   * @dev Note that if the provided user to burn from is delegating that the delegate's
   *      delegateBalance will be updated.
   * @dev Burn is expected to be called by the Vault.
   * @param _from The address to burn balance and delegateBalance from
   * @param _amount The amount to burn
   */
  function burn(address _from, uint96 _amount) external {
    _transferBalance(msg.sender, _from, address(0), _amount);
  }

  /**
   * @notice Transfers balance and delegateBalance from a given user.
   * @dev Note that if the provided user to transfer from is delegating that the delegate's
   *      delegateBalance will be updated.
   * @param _from The address to transfer the balance and delegateBalance from
   * @param _to The address to transfer balance and delegateBalance to
   * @param _amount The amount to transfer
   */
  function transfer(address _from, address _to, uint96 _amount) external {
    if (_to == address(0)) {
      revert TransferToZeroAddress();
    }
    _transferBalance(msg.sender, _from, _to, _amount);
  }

  /**
   * @notice Sets a delegate for a user which forwards the delegateBalance tied to the user's
   *          balance to the delegate's delegateBalance.
   * @param _vault The vault for which the delegate is being set
   * @param _to the address to delegate to
   */
  function delegate(address _vault, address _to) external {
    _delegate(_vault, msg.sender, _to);
  }

  /**
   * @notice Delegate user balance to the sponsorship address.
   * @dev Must only be called by the Vault contract.
   * @param _from Address of the user delegating their balance to the sponsorship address.
   */
  function sponsor(address _from) external {
    _delegate(msg.sender, _from, SPONSORSHIP_ADDRESS);
  }

  /* ============ Internal Functions ============ */

  /**
   * @notice Transfers a user's vault balance from one address to another.
   * @dev If the user is delegating, their delegate's delegateBalance is also updated.
   * @dev If we are minting or burning tokens then the total supply is also updated.
   * @param _vault the vault for which the balance is being transferred
   * @param _from the address from which the balance is being transferred
   * @param _to the address to which the balance is being transferred
   * @param _amount the amount of balance being transferred
   */
  function _transferBalance(address _vault, address _from, address _to, uint96 _amount) internal {
    if (_to == SPONSORSHIP_ADDRESS) {
      revert CannotTransferToSponsorshipAddress();
    }

    if (_from == _to) {
      return;
    }

    // If we are transferring tokens from a delegated account to an undelegated account
    address _fromDelegate = _delegateOf(_vault, _from);
    address _toDelegate = _delegateOf(_vault, _to);
    if (_from != address(0)) {
      bool _isFromDelegate = _fromDelegate == _from;

      _decreaseBalances(_vault, _from, _amount, _isFromDelegate ? _amount : 0);

      // If the user is not delegating to themself, decrease the delegate's delegateBalance
      // If the user is delegating to the sponsorship address, don't adjust the delegateBalance
      if (!_isFromDelegate && _fromDelegate != SPONSORSHIP_ADDRESS) {
        _decreaseBalances(_vault, _fromDelegate, 0, _amount);
      }

      // Burn balance if we're transferring to address(0)
      // Burn delegateBalance if we're transferring to address(0) and burning from an address that is not delegating to the sponsorship address
      // Burn delegateBalance if we're transferring to an address delegating to the sponsorship address from an address that isn't delegating to the sponsorship address
      if (
        _to == address(0) ||
        (_toDelegate == SPONSORSHIP_ADDRESS && _fromDelegate != SPONSORSHIP_ADDRESS)
      ) {
        // If the user is delegating to the sponsorship address, don't adjust the total supply delegateBalance
        _decreaseTotalSupplyBalances(
          _vault,
          _to == address(0) ? _amount : 0,
          (_to == address(0) && _fromDelegate != SPONSORSHIP_ADDRESS) ||
            (_toDelegate == SPONSORSHIP_ADDRESS && _fromDelegate != SPONSORSHIP_ADDRESS)
            ? _amount
            : 0
        );
      }
    }

    // If we are transferring tokens to an address other than address(0)
    if (_to != address(0)) {
      bool _isToDelegate = _toDelegate == _to;

      // If the user is delegating to themself, increase their delegateBalance
      _increaseBalances(_vault, _to, _amount, _isToDelegate ? _amount : 0);

      // Otherwise, increase their delegates delegateBalance if it is not the sponsorship address
      if (!_isToDelegate && _toDelegate != SPONSORSHIP_ADDRESS) {
        _increaseBalances(_vault, _toDelegate, 0, _amount);
      }

      // Mint balance if we're transferring from address(0)
      // Mint delegateBalance if we're transferring from address(0) and to an address not delegating to the sponsorship address
      // Mint delegateBalance if we're transferring from an address delegating to the sponsorship address to an address that isn't delegating to the sponsorship address
      if (
        _from == address(0) ||
        (_fromDelegate == SPONSORSHIP_ADDRESS && _toDelegate != SPONSORSHIP_ADDRESS)
      ) {
        _increaseTotalSupplyBalances(
          _vault,
          _from == address(0) ? _amount : 0,
          (_from == address(0) && _toDelegate != SPONSORSHIP_ADDRESS) ||
            (_fromDelegate == SPONSORSHIP_ADDRESS && _toDelegate != SPONSORSHIP_ADDRESS)
            ? _amount
            : 0
        );
      }
    }
  }

  /**
   * @notice Looks up the delegate of a user.
   * @param _vault the vault for which the user's delegate is being queried
   * @param _user the address to query the delegate of
   * @return The address of the user's delegate
   */
  function _delegateOf(address _vault, address _user) internal view returns (address) {
    address _userDelegate;

    if (_user != address(0)) {
      _userDelegate = delegates[_vault][_user];

      // If the user has not delegated, then the user is the delegate
      if (_userDelegate == address(0)) {
        _userDelegate = _user;
      }
    }

    return _userDelegate;
  }

  /**
   * @notice Transfers a user's vault delegateBalance from one address to another.
   * @param _vault the vault for which the delegateBalance is being transferred
   * @param _fromDelegate the address from which the delegateBalance is being transferred
   * @param _toDelegate the address to which the delegateBalance is being transferred
   * @param _amount the amount of delegateBalance being transferred
   */
  function _transferDelegateBalance(
    address _vault,
    address _fromDelegate,
    address _toDelegate,
    uint96 _amount
  ) internal {
    // If we are transferring tokens from a delegated account to an undelegated account
    if (_fromDelegate != address(0) && _fromDelegate != SPONSORSHIP_ADDRESS) {
      _decreaseBalances(_vault, _fromDelegate, 0, _amount);

      // If we are delegating to the zero address, decrease total supply
      // If we are delegating to the sponsorship address, decrease total supply
      if (_toDelegate == address(0) || _toDelegate == SPONSORSHIP_ADDRESS) {
        _decreaseTotalSupplyBalances(_vault, 0, _amount);
      }
    }

    // If we are transferring tokens from an undelegated account to a delegated account
    if (_toDelegate != address(0) && _toDelegate != SPONSORSHIP_ADDRESS) {
      _increaseBalances(_vault, _toDelegate, 0, _amount);

      // If we are removing delegation from the zero address, increase total supply
      // If we are removing delegation from the sponsorship address, increase total supply
      if (_fromDelegate == address(0) || _fromDelegate == SPONSORSHIP_ADDRESS) {
        _increaseTotalSupplyBalances(_vault, 0, _amount);
      }
    }
  }

  /**
   * @notice Sets a delegate for a user which forwards the delegateBalance tied to the user's
   * balance to the delegate's delegateBalance. "Sponsoring" means the funds aren't delegated
   * to anyone; this can be done by passing address(0) or the SPONSORSHIP_ADDRESS as the delegate.
   * @param _vault The vault for which the delegate is being set
   * @param _from the address to delegate from
   * @param _to the address to delegate to
   */
  function _delegate(address _vault, address _from, address _to) internal {
    address _currentDelegate = _delegateOf(_vault, _from);
    // address(0) is interpreted as sponsoring, so they don't need to know the sponsorship address.
    address to = _to == address(0) ? SPONSORSHIP_ADDRESS : _to;
    if (to == _currentDelegate) {
      revert SameDelegateAlreadySet(to);
    }

    delegates[_vault][_from] = to;

    _transferDelegateBalance(
      _vault,
      _currentDelegate,
      _to,
      SafeCast.toUint96(userObservations[_vault][_from].details.balance)
    );

    emit Delegated(_vault, _from, to);
  }

  /**
   * @notice Increases a user's balance and delegateBalance for a specific vault.
   * @param _vault the vault for which the balance is being increased
   * @param _user the address of the user whose balance is being increased
   * @param _amount the amount of balance being increased
   * @param _delegateAmount the amount of delegateBalance being increased
   */
  function _increaseBalances(
    address _vault,
    address _user,
    uint96 _amount,
    uint96 _delegateAmount
  ) internal {
    TwabLib.Account storage _account = userObservations[_vault][_user];

    (
      ObservationLib.Observation memory _observation,
      bool _isNewObservation,
      bool _isObservationRecorded,
      TwabLib.AccountDetails memory accountDetails
    ) = TwabLib.increaseBalances(PERIOD_LENGTH, PERIOD_OFFSET, _account, _amount, _delegateAmount);

    // Always emit the balance change event
    if (_amount != 0 || _delegateAmount != 0) {
      emit IncreasedBalance(_vault, _user, _amount, _delegateAmount);
    }

    // Conditionally emit the observation recorded event
    if (_isObservationRecorded) {
      emit ObservationRecorded(
        _vault,
        _user,
        accountDetails.balance,
        accountDetails.delegateBalance,
        _isNewObservation,
        _observation
      );
    }
  }

  /**
   * @notice Decreases the a user's balance and delegateBalance for a specific vault.
   * @param _vault the vault for which the totalSupply balance is being decreased
   * @param _amount the amount of balance being decreased
   * @param _delegateAmount the amount of delegateBalance being decreased
   */
  function _decreaseBalances(
    address _vault,
    address _user,
    uint96 _amount,
    uint96 _delegateAmount
  ) internal {
    TwabLib.Account storage _account = userObservations[_vault][_user];

    (
      ObservationLib.Observation memory _observation,
      bool _isNewObservation,
      bool _isObservationRecorded,
      TwabLib.AccountDetails memory accountDetails
    ) = TwabLib.decreaseBalances(
        PERIOD_LENGTH,
        PERIOD_OFFSET,
        _account,
        _amount,
        _delegateAmount,
        "TC/observation-burn-lt-delegate-balance"
      );

    // Always emit the balance change event
    if (_amount != 0 || _delegateAmount != 0) {
      emit DecreasedBalance(_vault, _user, _amount, _delegateAmount);
    }

    // Conditionally emit the observation recorded event
    if (_isObservationRecorded) {
      emit ObservationRecorded(
        _vault,
        _user,
        accountDetails.balance,
        accountDetails.delegateBalance,
        _isNewObservation,
        _observation
      );
    }
  }

  /**
   * @notice Decreases the totalSupply balance and delegateBalance for a specific vault.
   * @param _vault the vault for which the totalSupply balance is being decreased
   * @param _amount the amount of balance being decreased
   * @param _delegateAmount the amount of delegateBalance being decreased
   */
  function _decreaseTotalSupplyBalances(
    address _vault,
    uint96 _amount,
    uint96 _delegateAmount
  ) internal {
    TwabLib.Account storage _account = totalSupplyObservations[_vault];

    (
      ObservationLib.Observation memory _observation,
      bool _isNewObservation,
      bool _isObservationRecorded,
      TwabLib.AccountDetails memory accountDetails
    ) = TwabLib.decreaseBalances(
        PERIOD_LENGTH,
        PERIOD_OFFSET,
        _account,
        _amount,
        _delegateAmount,
        "TC/burn-amount-exceeds-total-supply-balance"
      );

    // Always emit the balance change event
    if (_amount != 0 || _delegateAmount != 0) {
      emit DecreasedTotalSupply(_vault, _amount, _delegateAmount);
    }

    // Conditionally emit the observation recorded event
    if (_isObservationRecorded) {
      emit TotalSupplyObservationRecorded(
        _vault,
        accountDetails.balance,
        accountDetails.delegateBalance,
        _isNewObservation,
        _observation
      );
    }
  }

  /**
   * @notice Increases the totalSupply balance and delegateBalance for a specific vault.
   * @param _vault the vault for which the totalSupply balance is being increased
   * @param _amount the amount of balance being increased
   * @param _delegateAmount the amount of delegateBalance being increased
   */
  function _increaseTotalSupplyBalances(
    address _vault,
    uint96 _amount,
    uint96 _delegateAmount
  ) internal {
    TwabLib.Account storage _account = totalSupplyObservations[_vault];

    (
      ObservationLib.Observation memory _observation,
      bool _isNewObservation,
      bool _isObservationRecorded,
      TwabLib.AccountDetails memory accountDetails
    ) = TwabLib.increaseBalances(PERIOD_LENGTH, PERIOD_OFFSET, _account, _amount, _delegateAmount);

    // Always emit the balance change event
    if (_amount != 0 || _delegateAmount != 0) {
      emit IncreasedTotalSupply(_vault, _amount, _delegateAmount);
    }

    // Conditionally emit the observation recorded event
    if (_isObservationRecorded) {
      emit TotalSupplyObservationRecorded(
        _vault,
        accountDetails.balance,
        accountDetails.delegateBalance,
        _isNewObservation,
        _observation
      );
    }
  }
}

File 2 of 5 : 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);
    }
}

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

import "ring-buffer-lib/RingBufferLib.sol";

import { ObservationLib, MAX_CARDINALITY } from "./ObservationLib.sol";

type PeriodOffsetRelativeTimestamp is uint32;

/// @notice Emitted when a balance is decreased by an amount that exceeds the amount available.
/// @param balance The current balance of the account
/// @param amount The amount being decreased from the account's balance
/// @param message An additional message describing the error
error BalanceLTAmount(uint96 balance, uint96 amount, string message);

/// @notice Emitted when a delegate balance is decreased by an amount that exceeds the amount available.
/// @param delegateBalance The current delegate balance of the account
/// @param delegateAmount The amount being decreased from the account's delegate balance
/// @param message An additional message describing the error
error DelegateBalanceLTAmount(uint96 delegateBalance, uint96 delegateAmount, string message);

/// @notice Emitted when a request is made for a twab that is not yet finalized.
/// @param timestamp The requested timestamp
/// @param currentOverwritePeriodStartedAt The current overwrite period start time
error TimestampNotFinalized(uint256 timestamp, uint256 currentOverwritePeriodStartedAt);

/// @notice Emitted when a TWAB time range start is after the end.
/// @param start The start time
/// @param end The end time
error InvalidTimeRange(uint256 start, uint256 end);

/// @notice Emitted when there is insufficient history to lookup a twab time range
/// @param requestedTimestamp The timestamp requested
/// @param oldestTimestamp The oldest timestamp that can be read
error InsufficientHistory(
  PeriodOffsetRelativeTimestamp requestedTimestamp,
  PeriodOffsetRelativeTimestamp oldestTimestamp
);

/**
 * @title  PoolTogether V5 TwabLib (Library)
 * @author PoolTogether Inc. & G9 Software Inc.
 * @dev    Time-Weighted Average Balance Library for ERC20 tokens.
 * @notice This TwabLib adds on-chain historical lookups to a user(s) time-weighted average balance.
 *         Each user is mapped to an Account struct containing the TWAB history (ring buffer) and
 *         ring buffer parameters. Every token.transfer() creates a new TWAB checkpoint. The new
 *         TWAB checkpoint is stored in the circular ring buffer, as either a new checkpoint or
 *         rewriting a previous checkpoint with new parameters. One checkpoint per day is stored.
 *         The TwabLib guarantees minimum 1 year of search history.
 * @notice There are limitations to the Observation data structure used. Ensure your token is
 *         compatible before using this library. Ensure the date ranges you're relying on are
 *         within safe boundaries.
 */
library TwabLib {
  /**
   * @notice Struct ring buffer parameters for single user Account.
   * @param balance Current token balance for an Account
   * @param delegateBalance Current delegate balance for an Account (active balance for chance)
   * @param nextObservationIndex Next uninitialized or updatable ring buffer checkpoint storage slot
   * @param cardinality Current total "initialized" ring buffer checkpoints for single user Account.
   *                    Used to set initial boundary conditions for an efficient binary search.
   */
  struct AccountDetails {
    uint96 balance;
    uint96 delegateBalance;
    uint16 nextObservationIndex;
    uint16 cardinality;
  }

  /**
   * @notice Account details and historical twabs.
   * @dev The size of observations is MAX_CARDINALITY from the ObservationLib.
   * @param details The account details
   * @param observations The history of observations for this account
   */
  struct Account {
    AccountDetails details;
    ObservationLib.Observation[17520] observations;
  }

  /**
   * @notice Increase a user's balance and delegate balance by a given amount.
   * @dev This function mutates the provided account.
   * @param PERIOD_LENGTH The length of an overwrite period
   * @param PERIOD_OFFSET The offset of the first period
   * @param _account The account to update
   * @param _amount The amount to increase the balance by
   * @param _delegateAmount The amount to increase the delegate balance by
   * @return observation The new/updated observation
   * @return isNew Whether or not the observation is new or overwrote a previous one
   * @return isObservationRecorded Whether or not an observation was recorded to storage
   */
  function increaseBalances(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    Account storage _account,
    uint96 _amount,
    uint96 _delegateAmount
  )
    internal
    returns (
      ObservationLib.Observation memory observation,
      bool isNew,
      bool isObservationRecorded,
      AccountDetails memory accountDetails
    )
  {
    accountDetails = _account.details;
    // record a new observation if the delegateAmount is non-zero and time has not overflowed.
    isObservationRecorded =
      _delegateAmount != uint96(0) &&
      block.timestamp <= lastObservationAt(PERIOD_LENGTH, PERIOD_OFFSET);

    accountDetails.balance += _amount;
    accountDetails.delegateBalance += _delegateAmount;

    // Only record a new Observation if the users delegateBalance has changed.
    if (isObservationRecorded) {
      (observation, isNew, accountDetails) = _recordObservation(
        PERIOD_LENGTH,
        PERIOD_OFFSET,
        accountDetails,
        _account
      );
    }

    _account.details = accountDetails;
  }

  /**
   * @notice Decrease a user's balance and delegate balance by a given amount.
   * @dev This function mutates the provided account.
   * @param PERIOD_LENGTH The length of an overwrite period
   * @param PERIOD_OFFSET The offset of the first period
   * @param _account The account to update
   * @param _amount The amount to decrease the balance by
   * @param _delegateAmount The amount to decrease the delegate balance by
   * @param _revertMessage The revert message to use if the balance is insufficient
   * @return observation The new/updated observation
   * @return isNew Whether or not the observation is new or overwrote a previous one
   * @return isObservationRecorded Whether or not the observation was recorded to storage
   */
  function decreaseBalances(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    Account storage _account,
    uint96 _amount,
    uint96 _delegateAmount,
    string memory _revertMessage
  )
    internal
    returns (
      ObservationLib.Observation memory observation,
      bool isNew,
      bool isObservationRecorded,
      AccountDetails memory accountDetails
    )
  {
    accountDetails = _account.details;

    if (accountDetails.balance < _amount) {
      revert BalanceLTAmount(accountDetails.balance, _amount, _revertMessage);
    }
    if (accountDetails.delegateBalance < _delegateAmount) {
      revert DelegateBalanceLTAmount(
        accountDetails.delegateBalance,
        _delegateAmount,
        _revertMessage
      );
    }

    // record a new observation if the delegateAmount is non-zero and time has not overflowed.
    isObservationRecorded =
      _delegateAmount != uint96(0) &&
      block.timestamp <= lastObservationAt(PERIOD_LENGTH, PERIOD_OFFSET);

    unchecked {
      accountDetails.balance -= _amount;
      accountDetails.delegateBalance -= _delegateAmount;
    }

    // Only record a new Observation if the users delegateBalance has changed.
    if (isObservationRecorded) {
      (observation, isNew, accountDetails) = _recordObservation(
        PERIOD_LENGTH,
        PERIOD_OFFSET,
        accountDetails,
        _account
      );
    }

    _account.details = accountDetails;
  }

  /**
   * @notice Looks up the oldest observation in the circular buffer.
   * @param _observations The circular buffer of observations
   * @param _accountDetails The account details to query with
   * @return index The index of the oldest observation
   * @return observation The oldest observation in the circular buffer
   */
  function getOldestObservation(
    ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
    AccountDetails memory _accountDetails
  ) internal view returns (uint16 index, ObservationLib.Observation memory observation) {
    // If the circular buffer has not been fully populated, we go to the beginning of the buffer at index 0.
    if (_accountDetails.cardinality < MAX_CARDINALITY) {
      index = 0;
      observation = _observations[0];
    } else {
      index = _accountDetails.nextObservationIndex;
      observation = _observations[index];
    }
  }

  /**
   * @notice Looks up the newest observation in the circular buffer.
   * @param _observations The circular buffer of observations
   * @param _accountDetails The account details to query with
   * @return index The index of the newest observation
   * @return observation The newest observation in the circular buffer
   */
  function getNewestObservation(
    ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
    AccountDetails memory _accountDetails
  ) internal view returns (uint16 index, ObservationLib.Observation memory observation) {
    index = uint16(
      RingBufferLib.newestIndex(_accountDetails.nextObservationIndex, MAX_CARDINALITY)
    );
    observation = _observations[index];
  }

  /**
   * @notice Looks up a users balance at a specific time in the past. The time must be before the current overwrite period.
   * @dev Ensure timestamps are safe using requireFinalized
   * @param PERIOD_LENGTH The length of an overwrite period
   * @param PERIOD_OFFSET The offset of the first period
   * @param _observations The circular buffer of observations
   * @param _accountDetails The account details to query with
   * @param _targetTime The time to look up the balance at
   * @return balance The balance at the target time
   */
  function getBalanceAt(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
    AccountDetails memory _accountDetails,
    uint256 _targetTime
  ) internal view requireFinalized(PERIOD_LENGTH, PERIOD_OFFSET, _targetTime) returns (uint256) {
    if (_targetTime < PERIOD_OFFSET) {
      return 0;
    }
    // if this is for an overflowed time period, return 0
    if (isShutdownAt(_targetTime, PERIOD_LENGTH, PERIOD_OFFSET)) {
      return 0;
    }
    ObservationLib.Observation memory prevOrAtObservation = _getPreviousOrAtObservation(
      _observations,
      _accountDetails,
      PeriodOffsetRelativeTimestamp.wrap(uint32(_targetTime - PERIOD_OFFSET))
    );
    return prevOrAtObservation.balance;
  }

  /**
   * @notice Returns whether the TwabController has been shutdown at the given timestamp
   * If the twab is queried at or after this time, whether an absolute timestamp or time range, it will return 0.
   * @param timestamp The timestamp to check
   * @param PERIOD_OFFSET The offset of the first period
   * @return True if the TwabController is shutdown at the given timestamp, false otherwise.
   */
  function isShutdownAt(
    uint256 timestamp,
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET
  ) internal pure returns (bool) {
    return timestamp > lastObservationAt(PERIOD_LENGTH, PERIOD_OFFSET);
  }

  /**
   * @notice Computes the largest timestamp at which the TwabController can record a new observation.
   * @param PERIOD_OFFSET The offset of the first period
   * @return The largest timestamp at which the TwabController can record a new observation.
   */
  function lastObservationAt(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET
  ) internal pure returns (uint256) {
    return uint256(PERIOD_OFFSET) + (type(uint32).max / PERIOD_LENGTH) * PERIOD_LENGTH;
  }

  /**
   * @notice Looks up a users TWAB for a time range. The time must be before the current overwrite period.
   * @dev If the timestamps in the range are not exact matches of observations, the balance is extrapolated using the previous observation.
   * @param PERIOD_LENGTH The length of an overwrite period
   * @param PERIOD_OFFSET The offset of the first period
   * @param _observations The circular buffer of observations
   * @param _accountDetails The account details to query with
   * @param _startTime The start of the time range
   * @param _endTime The end of the time range
   * @return twab The TWAB for the time range
   */
  function getTwabBetween(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
    AccountDetails memory _accountDetails,
    uint256 _startTime,
    uint256 _endTime
  ) internal view requireFinalized(PERIOD_LENGTH, PERIOD_OFFSET, _endTime) returns (uint256) {
    if (_endTime < _startTime) {
      revert InvalidTimeRange(_startTime, _endTime);
    }

    // if the range extends into the shutdown period, return 0
    if (isShutdownAt(_endTime, PERIOD_LENGTH, PERIOD_OFFSET)) {
      return 0;
    }

    uint256 offsetStartTime = _startTime - PERIOD_OFFSET;
    uint256 offsetEndTime = _endTime - PERIOD_OFFSET;

    ObservationLib.Observation memory endObservation = _getPreviousOrAtObservation(
      _observations,
      _accountDetails,
      PeriodOffsetRelativeTimestamp.wrap(uint32(offsetEndTime))
    );

    if (offsetStartTime == offsetEndTime) {
      return endObservation.balance;
    }

    ObservationLib.Observation memory startObservation = _getPreviousOrAtObservation(
      _observations,
      _accountDetails,
      PeriodOffsetRelativeTimestamp.wrap(uint32(offsetStartTime))
    );

    if (startObservation.timestamp != offsetStartTime) {
      startObservation = _calculateTemporaryObservation(
        startObservation,
        PeriodOffsetRelativeTimestamp.wrap(uint32(offsetStartTime))
      );
    }

    if (endObservation.timestamp != offsetEndTime) {
      endObservation = _calculateTemporaryObservation(
        endObservation,
        PeriodOffsetRelativeTimestamp.wrap(uint32(offsetEndTime))
      );
    }

    // Difference in amount / time
    return
      (endObservation.cumulativeBalance - startObservation.cumulativeBalance) /
      (offsetEndTime - offsetStartTime);
  }

  /**
   * @notice Given an AccountDetails with updated balances, either updates the latest Observation or records a new one
   * @param PERIOD_LENGTH The overwrite period length
   * @param PERIOD_OFFSET The overwrite period offset
   * @param _accountDetails The updated account details
   * @param _account The account to update
   * @return observation The new/updated observation
   * @return isNew Whether or not the observation is new or overwrote a previous one
   * @return newAccountDetails The new account details
   */
  function _recordObservation(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    AccountDetails memory _accountDetails,
    Account storage _account
  )
    internal
    returns (
      ObservationLib.Observation memory observation,
      bool isNew,
      AccountDetails memory newAccountDetails
    )
  {
    PeriodOffsetRelativeTimestamp currentTime = PeriodOffsetRelativeTimestamp.wrap(
      uint32(block.timestamp - PERIOD_OFFSET)
    );

    uint16 nextIndex;
    ObservationLib.Observation memory newestObservation;
    (nextIndex, newestObservation, isNew) = _getNextObservationIndex(
      PERIOD_LENGTH,
      PERIOD_OFFSET,
      _account.observations,
      _accountDetails
    );

    if (isNew) {
      // If the index is new, then we increase the next index to use
      _accountDetails.nextObservationIndex = uint16(
        RingBufferLib.nextIndex(uint256(nextIndex), MAX_CARDINALITY)
      );

      // Prevent the Account specific cardinality from exceeding the MAX_CARDINALITY.
      // The ring buffer length is limited by MAX_CARDINALITY. IF the account.cardinality
      // exceeds the max cardinality, new observations would be incorrectly set or the
      // observation would be out of "bounds" of the ring buffer. Once reached the
      // Account.cardinality will continue to be equal to max cardinality.
      _accountDetails.cardinality = _accountDetails.cardinality < MAX_CARDINALITY
        ? _accountDetails.cardinality + 1
        : MAX_CARDINALITY;
    }

    observation = ObservationLib.Observation({
      cumulativeBalance: _extrapolateFromBalance(newestObservation, currentTime),
      balance: _accountDetails.delegateBalance,
      timestamp: PeriodOffsetRelativeTimestamp.unwrap(currentTime)
    });

    // Write to storage
    _account.observations[nextIndex] = observation;
    newAccountDetails = _accountDetails;
  }

  /**
   * @notice Calculates a temporary observation for a given time using the previous observation.
   * @dev This is used to extrapolate a balance for any given time.
   * @param _observation The previous observation
   * @param _time The time to extrapolate to
   */
  function _calculateTemporaryObservation(
    ObservationLib.Observation memory _observation,
    PeriodOffsetRelativeTimestamp _time
  ) private pure returns (ObservationLib.Observation memory) {
    return
      ObservationLib.Observation({
        cumulativeBalance: _extrapolateFromBalance(_observation, _time),
        balance: _observation.balance,
        timestamp: PeriodOffsetRelativeTimestamp.unwrap(_time)
      });
  }

  /**
   * @notice Looks up the next observation index to write to in the circular buffer.
   * @dev If the current time is in the same period as the newest observation, we overwrite it.
   * @dev If the current time is in a new period, we increment the index and write a new observation.
   * @param PERIOD_LENGTH The length of an overwrite period
   * @param PERIOD_OFFSET The offset of the first period
   * @param _observations The circular buffer of observations
   * @param _accountDetails The account details to query with
   * @return index The index of the next observation slot to overwrite
   * @return newestObservation The newest observation in the circular buffer
   * @return isNew True if the observation slot is new, false if we're overwriting
   */
  function _getNextObservationIndex(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
    AccountDetails memory _accountDetails
  )
    private
    view
    returns (uint16 index, ObservationLib.Observation memory newestObservation, bool isNew)
  {
    uint16 newestIndex;
    (newestIndex, newestObservation) = getNewestObservation(_observations, _accountDetails);

    uint256 currentPeriod = getTimestampPeriod(PERIOD_LENGTH, PERIOD_OFFSET, block.timestamp);

    uint256 newestObservationPeriod = getTimestampPeriod(
      PERIOD_LENGTH,
      PERIOD_OFFSET,
      PERIOD_OFFSET + uint256(newestObservation.timestamp)
    );

    // Create a new Observation if it's the first period or the current time falls within a new period
    if (_accountDetails.cardinality == 0 || currentPeriod > newestObservationPeriod) {
      return (_accountDetails.nextObservationIndex, newestObservation, true);
    }

    // Otherwise, we're overwriting the current newest Observation
    return (newestIndex, newestObservation, false);
  }

  /**
   * @notice Computes the start time of the current overwrite period
   * @param PERIOD_LENGTH The length of an overwrite period
   * @param PERIOD_OFFSET The offset of the first period
   * @return The start time of the current overwrite period
   */
  function _currentOverwritePeriodStartedAt(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET
  ) private view returns (uint256) {
    uint256 period = getTimestampPeriod(PERIOD_LENGTH, PERIOD_OFFSET, block.timestamp);
    return getPeriodStartTime(PERIOD_LENGTH, PERIOD_OFFSET, period);
  }

  /**
   * @notice Calculates the next cumulative balance using a provided Observation and timestamp.
   * @param _observation The observation to extrapolate from
   * @param _offsetTimestamp The timestamp to extrapolate to
   * @return cumulativeBalance The cumulative balance at the timestamp
   */
  function _extrapolateFromBalance(
    ObservationLib.Observation memory _observation,
    PeriodOffsetRelativeTimestamp _offsetTimestamp
  ) private pure returns (uint128) {
    // new cumulative balance = provided cumulative balance (or zero) + (current balance * elapsed seconds)
    unchecked {
      return
        uint128(
          uint256(_observation.cumulativeBalance) +
            uint256(_observation.balance) *
            (PeriodOffsetRelativeTimestamp.unwrap(_offsetTimestamp) - _observation.timestamp)
        );
    }
  }

  /**
   * @notice Computes the overwrite period start time given the current time
   * @param PERIOD_LENGTH The length of an overwrite period
   * @param PERIOD_OFFSET The offset of the first period
   * @return The start time for the current overwrite period.
   */
  function currentOverwritePeriodStartedAt(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET
  ) internal view returns (uint256) {
    return _currentOverwritePeriodStartedAt(PERIOD_LENGTH, PERIOD_OFFSET);
  }

  /**
   * @notice Calculates the period a timestamp falls within.
   * @dev Timestamp prior to the PERIOD_OFFSET are considered to be in period 0.
   * @param PERIOD_LENGTH The length of an overwrite period
   * @param PERIOD_OFFSET The offset of the first period
   * @param _timestamp The timestamp to calculate the period for
   * @return period The period
   */
  function getTimestampPeriod(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    uint256 _timestamp
  ) internal pure returns (uint256) {
    if (_timestamp <= PERIOD_OFFSET) {
      return 0;
    }
    return (_timestamp - PERIOD_OFFSET) / uint256(PERIOD_LENGTH);
  }

  /**
   * @notice Calculates the start timestamp for a period
   * @param PERIOD_LENGTH The period length to use to calculate the period
   * @param PERIOD_OFFSET The period offset to use to calculate the period
   * @param _period The period to check
   * @return _timestamp The timestamp at which the period starts
   */
  function getPeriodStartTime(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    uint256 _period
  ) internal pure returns (uint256) {
    return _period * PERIOD_LENGTH + PERIOD_OFFSET;
  }

  /**
   * @notice Calculates the last timestamp for a period
   * @param PERIOD_LENGTH The period length to use to calculate the period
   * @param PERIOD_OFFSET The period offset to use to calculate the period
   * @param _period The period to check
   * @return _timestamp The timestamp at which the period ends
   */
  function getPeriodEndTime(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    uint256 _period
  ) internal pure returns (uint256) {
    return (_period + 1) * PERIOD_LENGTH + PERIOD_OFFSET;
  }

  /**
   * @notice Looks up the newest observation before or at a given timestamp.
   * @dev If an observation is available at the target time, it is returned. Otherwise, the newest observation before the target time is returned.
   * @param PERIOD_OFFSET The period offset to use to calculate the period
   * @param _observations The circular buffer of observations
   * @param _accountDetails The account details to query with
   * @param _targetTime The timestamp to look up
   * @return prevOrAtObservation The observation
   */
  function getPreviousOrAtObservation(
    uint32 PERIOD_OFFSET,
    ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
    AccountDetails memory _accountDetails,
    uint256 _targetTime
  ) internal view returns (ObservationLib.Observation memory prevOrAtObservation) {
    if (_targetTime < PERIOD_OFFSET) {
      return ObservationLib.Observation({ cumulativeBalance: 0, balance: 0, timestamp: 0 });
    }
    uint256 offsetTargetTime = _targetTime - PERIOD_OFFSET;
    // if this is for an overflowed time period, return 0
    if (offsetTargetTime > type(uint32).max) {
      return
        ObservationLib.Observation({
          cumulativeBalance: 0,
          balance: 0,
          timestamp: type(uint32).max
        });
    }
    prevOrAtObservation = _getPreviousOrAtObservation(
      _observations,
      _accountDetails,
      PeriodOffsetRelativeTimestamp.wrap(uint32(offsetTargetTime))
    );
  }

  /**
   * @notice Looks up the newest observation before or at a given timestamp.
   * @dev If an observation is available at the target time, it is returned. Otherwise, the newest observation before the target time is returned.
   * @param _observations The circular buffer of observations
   * @param _accountDetails The account details to query with
   * @param _offsetTargetTime The timestamp to look up (offset by the period offset)
   * @return prevOrAtObservation The observation
   */
  function _getPreviousOrAtObservation(
    ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
    AccountDetails memory _accountDetails,
    PeriodOffsetRelativeTimestamp _offsetTargetTime
  ) private view returns (ObservationLib.Observation memory prevOrAtObservation) {
    // If there are no observations, return a zeroed observation
    if (_accountDetails.cardinality == 0) {
      return ObservationLib.Observation({ cumulativeBalance: 0, balance: 0, timestamp: 0 });
    }

    uint16 oldestTwabIndex;

    (oldestTwabIndex, prevOrAtObservation) = getOldestObservation(_observations, _accountDetails);

    // if the requested time is older than the oldest observation
    if (PeriodOffsetRelativeTimestamp.unwrap(_offsetTargetTime) < prevOrAtObservation.timestamp) {
      // if the user didn't have any activity prior to the oldest observation, then we know they had a zero balance
      if (_accountDetails.cardinality < MAX_CARDINALITY) {
        return
          ObservationLib.Observation({
            cumulativeBalance: 0,
            balance: 0,
            timestamp: PeriodOffsetRelativeTimestamp.unwrap(_offsetTargetTime)
          });
      } else {
        // if we are missing their history, we must revert
        revert InsufficientHistory(
          _offsetTargetTime,
          PeriodOffsetRelativeTimestamp.wrap(prevOrAtObservation.timestamp)
        );
      }
    }

    // We know targetTime >= oldestObservation.timestamp because of the above if statement, so we can return here.
    if (_accountDetails.cardinality == 1) {
      return prevOrAtObservation;
    }

    // Find the newest observation
    (
      uint16 newestTwabIndex,
      ObservationLib.Observation memory afterOrAtObservation
    ) = getNewestObservation(_observations, _accountDetails);

    // if the target time is at or after the newest, return it
    if (PeriodOffsetRelativeTimestamp.unwrap(_offsetTargetTime) >= afterOrAtObservation.timestamp) {
      return afterOrAtObservation;
    }
    // if we know there is only 1 observation older than the newest
    if (_accountDetails.cardinality == 2) {
      return prevOrAtObservation;
    }

    // Otherwise, we perform a binarySearch to find the observation before or at the timestamp
    (prevOrAtObservation, oldestTwabIndex, afterOrAtObservation, newestTwabIndex) = ObservationLib
      .binarySearch(
        _observations,
        newestTwabIndex,
        oldestTwabIndex,
        PeriodOffsetRelativeTimestamp.unwrap(_offsetTargetTime),
        _accountDetails.cardinality
      );

    // If the afterOrAt is at, we can skip a temporary Observation computation by returning it here
    if (afterOrAtObservation.timestamp == PeriodOffsetRelativeTimestamp.unwrap(_offsetTargetTime)) {
      return afterOrAtObservation;
    }

    return prevOrAtObservation;
  }

  /**
   * @notice Checks if the given timestamp is safe to perform a historic balance lookup on.
   * @dev A timestamp is safe if it is before the current overwrite period
   * @param PERIOD_LENGTH The period length to use to calculate the period
   * @param PERIOD_OFFSET The period offset to use to calculate the period
   * @param _time The timestamp to check
   * @return isSafe Whether or not the timestamp is safe
   */
  function hasFinalized(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    uint256 _time
  ) internal view returns (bool) {
    return _hasFinalized(PERIOD_LENGTH, PERIOD_OFFSET, _time);
  }

  /**
   * @notice Checks if the given timestamp is safe to perform a historic balance lookup on.
   * @dev A timestamp is safe if it is on or before the current overwrite period start time
   * @param PERIOD_LENGTH The period length to use to calculate the period
   * @param PERIOD_OFFSET The period offset to use to calculate the period
   * @param _time The timestamp to check
   * @return isSafe Whether or not the timestamp is safe
   */
  function _hasFinalized(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    uint256 _time
  ) private view returns (bool) {
    // It's safe if equal to the overwrite period start time, because the cumulative balance won't be impacted
    return _time <= _currentOverwritePeriodStartedAt(PERIOD_LENGTH, PERIOD_OFFSET);
  }

  /**
   * @notice Checks if the given timestamp is safe to perform a historic balance lookup on.
   * @param PERIOD_LENGTH The period length to use to calculate the period
   * @param PERIOD_OFFSET The period offset to use to calculate the period
   * @param _timestamp The timestamp to check
   */
  modifier requireFinalized(
    uint32 PERIOD_LENGTH,
    uint32 PERIOD_OFFSET,
    uint256 _timestamp
  ) {
    // The current period can still be changed; so the start of the period marks the beginning of unsafe timestamps.
    uint256 overwritePeriodStartTime = _currentOverwritePeriodStartedAt(
      PERIOD_LENGTH,
      PERIOD_OFFSET
    );
    // timestamp == overwritePeriodStartTime doesn't matter, because the cumulative balance won't be impacted
    if (_timestamp > overwritePeriodStartTime) {
      revert TimestampNotFinalized(_timestamp, overwritePeriodStartTime);
    }
    _;
  }
}

File 4 of 5 : ObservationLib.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import "ring-buffer-lib/RingBufferLib.sol";

/**
 * @dev Sets max ring buffer length in the Account.observations Observation list.
 *         As users transfer/mint/burn tickets new Observation checkpoints are recorded.
 *         The current `MAX_CARDINALITY` guarantees a one year minimum, of accurate historical lookups.
 * @dev The user Account.Account.cardinality parameter can NOT exceed the max cardinality variable.
 *      Preventing "corrupted" ring buffer lookup pointers and new observation checkpoints.
 */
uint16 constant MAX_CARDINALITY = 17520; // with min period of 1 hour, this allows for minimum two years of history

/**
 * @title PoolTogether V5 Observation Library
 * @author PoolTogether Inc. & G9 Software Inc.
 * @notice This library allows one to store an array of timestamped values and efficiently search them.
 * @dev Largely pulled from Uniswap V3 Oracle.sol: https://github.com/Uniswap/v3-core/blob/c05a0e2c8c08c460fb4d05cfdda30b3ad8deeaac/contracts/libraries/Oracle.sol
 */
library ObservationLib {
  /**
   * @notice Observation, which includes an amount and timestamp.
   * @param cumulativeBalance the cumulative time-weighted balance at `timestamp`.
   * @param balance `balance` at `timestamp`.
   * @param timestamp Recorded `timestamp`.
   */
  struct Observation {
    uint128 cumulativeBalance;
    uint96 balance;
    uint32 timestamp;
  }

  /**
   * @notice Fetches Observations `beforeOrAt` and `afterOrAt` a `_target`, eg: where [`beforeOrAt`, `afterOrAt`] is satisfied.
   * The result may be the same Observation, or adjacent Observations.
   * @dev The _target must fall within the boundaries of the provided _observations.
   * Meaning the _target must be: older than the most recent Observation and younger, or the same age as, the oldest Observation.
   * @dev  If `_newestObservationIndex` is less than `_oldestObservationIndex`, it means that we've wrapped around the circular buffer.
   *       So the most recent observation will be at `_oldestObservationIndex + _cardinality - 1`, at the beginning of the circular buffer.
   * @param _observations List of Observations to search through.
   * @param _newestObservationIndex Index of the newest Observation. Right side of the circular buffer.
   * @param _oldestObservationIndex Index of the oldest Observation. Left side of the circular buffer.
   * @param _target Timestamp at which we are searching the Observation.
   * @param _cardinality Cardinality of the circular buffer we are searching through.
   * @return beforeOrAt Observation recorded before, or at, the target.
   * @return beforeOrAtIndex Index of observation recorded before, or at, the target.
   * @return afterOrAt Observation recorded at, or after, the target.
   * @return afterOrAtIndex Index of observation recorded at, or after, the target.
   */
  function binarySearch(
    Observation[MAX_CARDINALITY] storage _observations,
    uint24 _newestObservationIndex,
    uint24 _oldestObservationIndex,
    uint32 _target,
    uint16 _cardinality
  )
    internal
    view
    returns (
      Observation memory beforeOrAt,
      uint16 beforeOrAtIndex,
      Observation memory afterOrAt,
      uint16 afterOrAtIndex
    )
  {
    uint256 leftSide = _oldestObservationIndex;
    uint256 rightSide = _newestObservationIndex < leftSide
      ? leftSide + _cardinality - 1
      : _newestObservationIndex;
    uint256 currentIndex;

    while (true) {
      // We start our search in the middle of the `leftSide` and `rightSide`.
      // After each iteration, we narrow down the search to the left or the right side while still starting our search in the middle.
      currentIndex = (leftSide + rightSide) / 2;

      beforeOrAtIndex = uint16(RingBufferLib.wrap(currentIndex, _cardinality));
      beforeOrAt = _observations[beforeOrAtIndex];
      uint32 beforeOrAtTimestamp = beforeOrAt.timestamp;

      afterOrAtIndex = uint16(RingBufferLib.nextIndex(currentIndex, _cardinality));
      afterOrAt = _observations[afterOrAtIndex];

      bool targetAfterOrAt = beforeOrAtTimestamp <= _target;

      // Check if we've found the corresponding Observation.
      if (targetAfterOrAt && _target <= afterOrAt.timestamp) {
        break;
      }

      // If `beforeOrAtTimestamp` is greater than `_target`, then we keep searching lower. To the left of the current index.
      if (!targetAfterOrAt) {
        rightSide = currentIndex - 1;
      } else {
        // Otherwise, we keep searching higher. To the right of the current index.
        leftSide = currentIndex + 1;
      }
    }
  }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

/**
 * NOTE: There is a difference in meaning between "cardinality" and "count":
 *  - cardinality is the physical size of the ring buffer (i.e. max elements).
 *  - count is the number of elements in the buffer, which may be less than cardinality.
 */
library RingBufferLib {
    /**
    * @notice Returns wrapped TWAB index.
    * @dev  In order to navigate the TWAB circular buffer, we need to use the modulo operator.
    * @dev  For example, if `_index` is equal to 32 and the TWAB circular buffer is of `_cardinality` 32,
    *       it will return 0 and will point to the first element of the array.
    * @param _index Index used to navigate through the TWAB circular buffer.
    * @param _cardinality TWAB buffer cardinality.
    * @return TWAB index.
    */
    function wrap(uint256 _index, uint256 _cardinality) internal pure returns (uint256) {
        return _index % _cardinality;
    }

    /**
    * @notice Computes the negative offset from the given index, wrapped by the cardinality.
    * @dev  We add `_cardinality` to `_index` to be able to offset even if `_amount` is superior to `_cardinality`.
    * @param _index The index from which to offset
    * @param _amount The number of indices to offset.  This is subtracted from the given index.
    * @param _count The number of elements in the ring buffer
    * @return Offsetted index.
     */
    function offset(
        uint256 _index,
        uint256 _amount,
        uint256 _count
    ) internal pure returns (uint256) {
        return wrap(_index + _count - _amount, _count);
    }

    /// @notice Returns the index of the last recorded TWAB
    /// @param _nextIndex The next available twab index.  This will be recorded to next.
    /// @param _count The count of the TWAB history.
    /// @return The index of the last recorded TWAB
    function newestIndex(uint256 _nextIndex, uint256 _count)
        internal
        pure
        returns (uint256)
    {
        if (_count == 0) {
            return 0;
        }

        return wrap(_nextIndex + _count - 1, _count);
    }

    function oldestIndex(uint256 _nextIndex, uint256 _count, uint256 _cardinality)
        internal
        pure
        returns (uint256)
    {
        if (_count < _cardinality) {
            return 0;
        } else {
            return wrap(_nextIndex + _cardinality, _cardinality);
        }
    }

    /// @notice Computes the ring buffer index that follows the given one, wrapped by cardinality
    /// @param _index The index to increment
    /// @param _cardinality The number of elements in the Ring Buffer
    /// @return The next index relative to the given index.  Will wrap around to 0 if the next index == cardinality
    function nextIndex(uint256 _index, uint256 _cardinality)
        internal
        pure
        returns (uint256)
    {
        return wrap(_index + 1, _cardinality);
    }

    /// @notice Computes the ring buffer index that preceeds the given one, wrapped by cardinality
    /// @param _index The index to increment
    /// @param _cardinality The number of elements in the Ring Buffer
    /// @return The prev index relative to the given index.  Will wrap around to the end if the prev index == 0
    function prevIndex(uint256 _index, uint256 _cardinality)
    internal
    pure
    returns (uint256) 
    {
        return _index == 0 ? _cardinality - 1 : _index - 1;
    }
}

Settings
{
  "remappings": [
    "ds-test/=lib/pt-v5-mainnet/lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/pt-v5-mainnet/lib/forge-std/src/",
    "openzeppelin-contracts/=lib/pt-v5-mainnet/lib/openzeppelin-contracts/contracts/",
    "openzeppelin-contracts/contracts/=lib/pt-v5-mainnet/lib/openzeppelin-contracts/contracts/",
    "prb-math/=lib/pt-v5-mainnet/lib/pt-v5-prize-pool/lib/prb-math/src/",
    "pt-v5-draw-manager/=lib/pt-v5-mainnet/lib/pt-v5-draw-manager/src/",
    "pt-v5-rng-witnet/=lib/pt-v5-mainnet/lib/pt-v5-rng-witnet/src/",
    "pt-v5-staking-vault/=lib/pt-v5-mainnet/lib/pt-v5-staking-vault/src/",
    "pt-v5-tpda-liquidator/=lib/pt-v5-mainnet/lib/pt-v5-tpda-liquidator/src/",
    "pt-v5-liquidator-interfaces/=lib/pt-v5-mainnet/lib/pt-v5-tpda-liquidator/lib/pt-v5-liquidator-interfaces/src/interfaces/",
    "pt-v5-prize-pool/=lib/pt-v5-mainnet/lib/pt-v5-prize-pool/src/",
    "pt-v5-twab-controller/=lib/pt-v5-mainnet/lib/pt-v5-twab-controller/src/",
    "pt-v5-twab-rewards/=lib/pt-v5-mainnet/lib/pt-v5-twab-rewards/src/",
    "pt-v5-vault/=lib/pt-v5-mainnet/lib/pt-v5-vault/src/",
    "pt-v5-claimer/=lib/pt-v5-mainnet/lib/pt-v5-claimer/src/",
    "pt-v5-rng-blockhash/=lib/pt-v5-rng-blockhash/src/",
    "pt-v5-twab-delegator/=lib/pt-v5-twab-delegator/src/",
    "pt-v5-vault-boost/=lib/pt-v5-vault-boost/src/",
    "owner-manager-contracts/=lib/pt-v5-mainnet/lib/pt-v5-vault/lib/owner-manager-contracts/contracts/",
    "@openzeppelin/contracts/=lib/pt-v5-rng-blockhash/lib/pt-v5-draw-manager/lib/openzeppelin-contracts/contracts/",
    "@prb/test/=lib/pt-v5-vault-boost/lib/prb-math/node_modules/@prb/test/",
    "ExcessivelySafeCall/=lib/pt-v5-twab-delegator/lib/pt-v5-vault/lib/ExcessivelySafeCall/src/",
    "brokentoken/=lib/pt-v5-twab-delegator/lib/pt-v5-vault/lib/brokentoken/src/",
    "create3-factory/=lib/pt-v5-mainnet/lib/yield-daddy/lib/create3-factory/",
    "erc4626-tests/=lib/pt-v5-twab-delegator/lib/openzeppelin-contracts/lib/erc4626-tests/",
    "excessively-safe-call/=lib/pt-v5-twab-delegator/lib/pt-v5-vault/lib/ExcessivelySafeCall/src/",
    "halmos-cheatcodes/=lib/pt-v5-mainnet/lib/pt-v5-draw-manager/lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
    "openzeppelin/=lib/pt-v5-twab-delegator/lib/openzeppelin-contracts/contracts/",
    "prb-test/=lib/pt-v5-vault-boost/lib/prb-math/lib/prb-test/src/",
    "pt-v5-claimable-interface/=lib/pt-v5-twab-delegator/lib/pt-v5-vault/lib/pt-v5-claimable-interface/src/",
    "pt-v5-mainnet/=lib/pt-v5-mainnet/",
    "ring-buffer-lib/=lib/pt-v5-twab-delegator/lib/pt-v5-twab-controller/lib/ring-buffer-lib/src/",
    "solady/=lib/pt-v5-mainnet/lib/pt-v5-rng-witnet/lib/solady/src/",
    "solmate/=lib/pt-v5-mainnet/lib/yield-daddy/lib/solmate/src/",
    "uniform-random-number/=lib/pt-v5-mainnet/lib/pt-v5-prize-pool/lib/uniform-random-number/src/",
    "weird-erc20/=lib/pt-v5-twab-delegator/lib/pt-v5-vault/lib/brokentoken/lib/weird-erc20/src/",
    "witnet-solidity-bridge/=lib/pt-v5-mainnet/lib/pt-v5-rng-witnet/lib/witnet-solidity-bridge/contracts/",
    "witnet/=lib/pt-v5-mainnet/lib/pt-v5-rng-witnet/lib/witnet-solidity-bridge/contracts/",
    "yield-daddy/=lib/pt-v5-mainnet/lib/yield-daddy/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200,
    "details": {
      "peephole": true,
      "inliner": true,
      "deduplicate": true,
      "cse": true,
      "yul": true
    }
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "paris",
  "viaIR": false,
  "libraries": {}
}

Contract ABI

API
[{"inputs":[{"internalType":"uint32","name":"_periodLength","type":"uint32"},{"internalType":"uint32","name":"_periodOffset","type":"uint32"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint96","name":"amount","type":"uint96"},{"internalType":"string","name":"message","type":"string"}],"name":"BalanceLTAmount","type":"error"},{"inputs":[],"name":"CannotTransferToSponsorshipAddress","type":"error"},{"inputs":[{"internalType":"uint96","name":"delegateBalance","type":"uint96"},{"internalType":"uint96","name":"delegateAmount","type":"uint96"},{"internalType":"string","name":"message","type":"string"}],"name":"DelegateBalanceLTAmount","type":"error"},{"inputs":[{"internalType":"PeriodOffsetRelativeTimestamp","name":"requestedTimestamp","type":"uint32"},{"internalType":"PeriodOffsetRelativeTimestamp","name":"oldestTimestamp","type":"uint32"}],"name":"InsufficientHistory","type":"error"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"InvalidTimeRange","type":"error"},{"inputs":[],"name":"PeriodLengthTooShort","type":"error"},{"inputs":[{"internalType":"uint32","name":"periodOffset","type":"uint32"}],"name":"PeriodOffsetInFuture","type":"error"},{"inputs":[{"internalType":"address","name":"delegate","type":"address"}],"name":"SameDelegateAlreadySet","type":"error"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"currentOverwritePeriodStartedAt","type":"uint256"}],"name":"TimestampNotFinalized","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint96","name":"amount","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"delegateAmount","type":"uint96"}],"name":"DecreasedBalance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"uint96","name":"amount","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"delegateAmount","type":"uint96"}],"name":"DecreasedTotalSupply","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"delegate","type":"address"}],"name":"Delegated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint96","name":"amount","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"delegateAmount","type":"uint96"}],"name":"IncreasedBalance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"uint96","name":"amount","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"delegateAmount","type":"uint96"}],"name":"IncreasedTotalSupply","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint96","name":"balance","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"delegateBalance","type":"uint96"},{"indexed":false,"internalType":"bool","name":"isNew","type":"bool"},{"components":[{"internalType":"uint128","name":"cumulativeBalance","type":"uint128"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"indexed":false,"internalType":"struct ObservationLib.Observation","name":"observation","type":"tuple"}],"name":"ObservationRecorded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"vault","type":"address"},{"indexed":false,"internalType":"uint96","name":"balance","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"delegateBalance","type":"uint96"},{"indexed":false,"internalType":"bool","name":"isNew","type":"bool"},{"components":[{"internalType":"uint128","name":"cumulativeBalance","type":"uint128"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"indexed":false,"internalType":"struct ObservationLib.Observation","name":"observation","type":"tuple"}],"name":"TotalSupplyObservationRecorded","type":"event"},{"inputs":[],"name":"PERIOD_LENGTH","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERIOD_OFFSET","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint96","name":"_amount","type":"uint96"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentOverwritePeriodStartedAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_vault","type":"address"},{"internalType":"address","name":"_to","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"delegateBalanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"delegateOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getAccount","outputs":[{"components":[{"components":[{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint96","name":"delegateBalance","type":"uint96"},{"internalType":"uint16","name":"nextObservationIndex","type":"uint16"},{"internalType":"uint16","name":"cardinality","type":"uint16"}],"internalType":"struct TwabLib.AccountDetails","name":"details","type":"tuple"},{"components":[{"internalType":"uint128","name":"cumulativeBalance","type":"uint128"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct ObservationLib.Observation[17520]","name":"observations","type":"tuple[17520]"}],"internalType":"struct TwabLib.Account","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"periodEndOnOrAfterTime","type":"uint256"}],"name":"getBalanceAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getNewestObservation","outputs":[{"internalType":"uint16","name":"","type":"uint16"},{"components":[{"internalType":"uint128","name":"cumulativeBalance","type":"uint128"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct ObservationLib.Observation","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"getNewestTotalSupplyObservation","outputs":[{"internalType":"uint16","name":"","type":"uint16"},{"components":[{"internalType":"uint128","name":"cumulativeBalance","type":"uint128"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct ObservationLib.Observation","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getOldestObservation","outputs":[{"internalType":"uint16","name":"","type":"uint16"},{"components":[{"internalType":"uint128","name":"cumulativeBalance","type":"uint128"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct ObservationLib.Observation","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"getOldestTotalSupplyObservation","outputs":[{"internalType":"uint16","name":"","type":"uint16"},{"components":[{"internalType":"uint128","name":"cumulativeBalance","type":"uint128"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct ObservationLib.Observation","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"time","type":"uint256"}],"name":"getTimestampPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"getTotalSupplyAccount","outputs":[{"components":[{"components":[{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint96","name":"delegateBalance","type":"uint96"},{"internalType":"uint16","name":"nextObservationIndex","type":"uint16"},{"internalType":"uint16","name":"cardinality","type":"uint16"}],"internalType":"struct TwabLib.AccountDetails","name":"details","type":"tuple"},{"components":[{"internalType":"uint128","name":"cumulativeBalance","type":"uint128"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct ObservationLib.Observation[17520]","name":"observations","type":"tuple[17520]"}],"internalType":"struct TwabLib.Account","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"periodEndOnOrAfterTime","type":"uint256"}],"name":"getTotalSupplyAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"getTotalSupplyTwabBetween","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"}],"name":"getTwabBetween","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"time","type":"uint256"}],"name":"hasFinalized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"isShutdownAt","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastObservationAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint96","name":"_amount","type":"uint96"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timestamp","type":"uint256"}],"name":"periodEndOnOrAfter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"}],"name":"sponsor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"vault","type":"address"}],"name":"totalSupplyDelegateBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint96","name":"_amount","type":"uint96"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60c06040523480156200001157600080fd5b5060405162003064380380620030648339810160408190526200003491620000c0565b610e1063ffffffff831610156200005d576040516238782760e91b815260040160405180910390fd5b428163ffffffff161115620000915760405163f2bcecad60e01b815263ffffffff8216600482015260240160405180910390fd5b63ffffffff9182166080521660a052620000f8565b805163ffffffff81168114620000bb57600080fd5b919050565b60008060408385031215620000d457600080fd5b620000df83620000a6565b9150620000ef60208401620000a6565b90509250929050565b60805160a051612e586200020c60003960008181610346015281816105c6015281816106510152818161070f01528181610776015281816109ec01528181610abc01528181610b1901528181610bfa01528181610d9e0152818161123f01528181611274015281816112c00152818161132d0152818161137a015281816118b701528181611a3a01528181611ba40152611c8b01526000818161047f015281816105a501528181610630015281816106ed01528181610755015281816109cb01528181610a9a01528181610af801528181610bd801528181610d7c015281816112990152818161130c015281816113590152818161189601528181611a1901528181611b830152611c6a0152612e586000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c8063766c4f37116100f9578063be00792911610097578063e7a891b911610071578063e7a891b91461047a578063e7d7b225146104a1578063f7888aec146104b4578063fd590847146104f457600080fd5b8063be00792914610422578063c661667d14610435578063e4dc2aa41461044857600080fd5b80638ab65686116100d35780638ab65686146103be5780638df2c8e6146103d1578063b5f783a8146103e4578063bb35799a1461040f57600080fd5b8063766c4f37146103905780637b2037cd146103a3578063805965f9146103b657600080fd5b80633aaa523211610166578063495b774611610140578063495b7746146102f15780634a5958ba146102f95780634c08d8e81461034157806372a142d01461037d57600080fd5b80633aaa5232146102855780633d594151146102be57806347c6394a146102de57600080fd5b80631fa57c25116101a25780631fa57c2514610229578063224b5c341461024c5780632d9d91f41461025f5780633621b5a91461027257600080fd5b80630b6511f2146101c957806310d14438146101f35780631b025a4014610214575b600080fd5b6101dc6101d736600461294b565b610507565b6040516101ea929190612997565b60405180910390f35b6102066102013660046129af565b61059e565b6040519081526020016101ea565b6102276102223660046129df565b6105f1565b005b61023c6102373660046129af565b610629565b60405190151581526020016101ea565b61020661025a366004612a12565b610676565b61023c61026d3660046129af565b61074d565b6101dc61028036600461294b565b61079a565b61020661029336600461294b565b6001600160a01b0316600090815260016020526040902054600160601b90046001600160601b031690565b6102d16102cc36600461294b565b610827565b6040516101ea9190612a4e565b6101dc6102ec366004612ace565b61091e565b6102066109c4565b610206610307366004612ace565b6001600160a01b0382811660009081526020818152604080832093851683529290522054600160601b90046001600160601b031692915050565b6103687f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff90911681526020016101ea565b61020661038b3660046129af565b610a15565b61022761039e36600461294b565b610a20565b6102066103b1366004612af8565b610a2f565b610206610af1565b6102276103cc366004612ace565b610b3d565b6102276103df3660046129df565b610b48565b6103f76103f2366004612ace565b610b55565b6040516001600160a01b0390911681526020016101ea565b61020661041d366004612b22565b610b61565b6101dc610430366004612ace565b610c40565b610227610443366004612b64565b610cd9565b61020661045636600461294b565b6001600160a01b03166000908152600160205260409020546001600160601b031690565b6103687f000000000000000000000000000000000000000000000000000000000000000081565b6102066104af366004612ba7565b610d11565b6102066104c2366004612ace565b6001600160a01b039182166000908152602081815260408083209390941682529190915220546001600160601b031690565b6102d1610502366004612ace565b610dcb565b604080516060810182526000808252602082018190529181018290526001600160a01b038316600090815260016020818152604092839020835160808101855281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811695820195909552600160d01b909204909316606082015261059491830190610ecf565b9250925050915091565b60006105eb7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000084610f66565b92915050565b6001600160a01b03821661061857604051633a954ecd60e21b815260040160405180910390fd5b6106253360008484610fa0565b5050565b60006105eb7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000008461122e565b6001600160a01b038084166000908152602081815260408083209386168352928152828220835160808101855281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811695820195909552600160d01b9092049093166060820152909190610742907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600185019061073d8861123b565b61139f565b9150505b9392505050565b60006105eb827f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000061144c565b604080516060810182526000808252602082018190529181018290526001600160a01b038316600090815260016020818152604092839020835160808101855281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811695820195909552600160d01b909204909316606082015261059491830190611462565b61082f6128b1565b6001600160a01b0382166000908152600160208181526040808420815160c08101835281546001600160601b03808216838601908152600160601b8304909116606084015261ffff600160c01b830481166080850152600160d01b90920490911660a08301528152825162088e0081019093529490939285019284019061447090835b828210156109105760408051606081018252858401546001600160801b0381168252600160801b81046001600160601b0316602080840191909152600160e01b90910463ffffffff16928201929092528252600190920191016108b2565b505050915250909392505050565b604080516060810182526000808252602082018190529181018290526001600160a01b03808516600090815260208181526040808320938716835292815290829020825160808101845281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811694820194909452600160d01b90920490921660608201526109b7906001830190610ecf565b92509250505b9250929050565b6000610a107f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006114fd565b905090565b60006105eb8261123b565b610a2c3382600161152f565b50565b6001600160a01b0382166000908152600160208181526040808420815160808101835281546001600160601b038082168352600160601b8204169482019490945261ffff600160c01b8504811693820193909352600160d01b909304909116606083015291610ae9917f0000000000000000000000000000000000000000000000000000000000000000917f0000000000000000000000000000000000000000000000000000000000000000919085019061073d8861123b565b949350505050565b6000610a107f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000611659565b61062582338361152f565b6106253383600084610fa0565b60006107468383611665565b6001600160a01b038085166000908152602081815260408083209387168352928152828220835160808101855281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811695820195909552600160d01b9092049093166060820152909190610c36907f0000000000000000000000000000000000000000000000000000000000000000907f0000000000000000000000000000000000000000000000000000000000000000906001850190610c288961123b565b610c318961123b565b6116ad565b9695505050505050565b604080516060810182526000808252602082018190529181018290526001600160a01b03808516600090815260208181526040808320938716835292815290829020825160808101845281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811694820194909452600160d01b90920490921660608201526109b7906001830190611462565b6001600160a01b038216610d0057604051633a954ecd60e21b815260040160405180910390fd5b610d0c33848484610fa0565b505050565b6001600160a01b0383166000908152600160208181526040808420815160808101835281546001600160601b038082168352600160601b8204169482019490945261ffff600160c01b8504811693820193909352600160d01b909304909116606083015291610742917f0000000000000000000000000000000000000000000000000000000000000000917f00000000000000000000000000000000000000000000000000000000000000009190850190610c288961123b565b610dd36128b1565b6001600160a01b038084166000908152602081815260408083209386168352928152828220835160c08101855281546001600160601b03808216838801908152600160601b8304909116606084015261ffff600160c01b830481166080850152600160d01b90920490911660a08301528152845162088e00810190955293909291840191600184019061447090835b82821015610ec05760408051606081018252858401546001600160801b0381168252600160801b81046001600160601b0316602080840191909152600160e01b90910463ffffffff1692820192909252825260019092019101610e62565b50505091525090949350505050565b604080516060810182526000808252602082018190529181018290526040830151610f009061ffff1661447061180f565b9150838261ffff166144708110610f1957610f19612bda565b6040805160608101825292909101546001600160801b03811683526001600160601b03600160801b820416602084015263ffffffff600160e01b9091041690820152919491935090915050565b60008263ffffffff168211610f7d57506000610746565b8363ffffffff168363ffffffff1683610f969190612c06565b610ae99190612c2f565b6000196001600160a01b03831601610fcb57604051635bb7132160e11b815260040160405180910390fd5b816001600160a01b0316836001600160a01b03160315611228576000610ff18585611665565b90506000610fff8685611665565b90506001600160a01b03851615611113576001600160a01b038281169086161461103787878684611031576000611840565b87611840565b8015801561104f57506001600160a01b038316600114155b15611061576110618784600087611840565b6001600160a01b038516158061109557506001600160a01b038216600114801561109557506001600160a01b038316600114155b1561111157611111876001600160a01b038716156110b45760006110b6565b855b6001600160a01b0388161580156110d757506001600160a01b038616600114155b8061110057506001600160a01b038516600114801561110057506001600160a01b038616600114155b61110b5760006119e5565b866119e5565b505b6001600160a01b03841615611225576001600160a01b038181169085161461114987868684611143576000611b55565b87611b55565b8015801561116157506001600160a01b038216600114155b15611173576111738783600087611b55565b6001600160a01b03861615806111a757506001600160a01b03831660011480156111a757506001600160a01b038216600114155b1561122357611223876001600160a01b038816156111c65760006111c8565b855b6001600160a01b0389161580156111e957506001600160a01b038516600114155b8061121257506001600160a01b038616600114801561121257506001600160a01b038516600114155b61121d576000611c49565b86611c49565b505b50505b50505050565b6000610ae9848484611d2b565b60007f000000000000000000000000000000000000000000000000000000000000000063ffffffff1682101561129757505063ffffffff7f00000000000000000000000000000000000000000000000000000000000000001690565b7f000000000000000000000000000000000000000000000000000000000000000063ffffffff167f000000000000000000000000000000000000000000000000000000000000000063ffffffff16836112f09190612c06565b6112fa9190612c43565b600003611305575090565b60006113527f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000085610f66565b90506107467f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000083611d42565b600085858360006113b08484611d6e565b9050808211156113e25760405163947ad91360e01b815260048101839052602481018290526044015b60405180910390fd5b8863ffffffff168610156113f9576000945061143f565b611404868b8b61144c565b15611412576000945061143f565b600061142e898961142963ffffffff8e168b612c06565b611d89565b602001516001600160601b03169550505b5050505095945050505050565b600061145883836114fd565b9093119392505050565b6040805160608101825260008082526020820181905291810182905261447061ffff16836060015161ffff1610156114df5750506040805160608101825283546001600160801b0381168252600160801b81046001600160601b03166020830152600160e01b900463ffffffff16918101919091526000906109bd565b82604001519150838261ffff166144708110610f1957610f19612bda565b60008261150e8163ffffffff612c57565b6115189190612c7a565b63ffffffff168263ffffffff166107469190612ca2565b600061153b8484611665565b905060006001600160a01b038316156115545782611557565b60015b9050816001600160a01b0316816001600160a01b03160361159657604051634929aa6560e01b81526001600160a01b03821660048201526024016113d9565b6001600160a01b03858116600081815260026020908152604080832089861680855290835281842080546001600160a01b031916968816969096179095559282528181528282209382529290925290205461160890869084908690611603906001600160601b0316611f1f565b611f8b565b806001600160a01b0316846001600160a01b0316866001600160a01b03167f2190b8902ea4a5dbea665e1965f2b2c0b04788c8831da4d881b56ddc9ead4fe860405160405180910390a45050505050565b60006107468383611d6e565b6000806001600160a01b0383161561074657506001600160a01b0380841660009081526002602090815260408083208685168452909152902054168061074657509092915050565b600086868360006116be8484611d6e565b9050808211156116eb5760405163947ad91360e01b815260048101839052602481018290526044016113d9565b8686101561171657604051630e781b2360e31b815260048101889052602481018790526044016113d9565b611721868c8c61144c565b1561172f5760009450611801565b600061174163ffffffff8c1689612c06565b9050600061175563ffffffff8d1689612c06565b905060006117648c8c84611d89565b905081830361178457602001516001600160601b03169650611801915050565b60006117918d8d86611d89565b905083816040015163ffffffff16146117b1576117ae8185612053565b90505b82826040015163ffffffff16146117cf576117cc8284612053565b91505b6117d98484612c06565b815183516117e79190612cb5565b6001600160801b03166117fa9190612c2f565b9850505050505b505050509695505050505050565b600081600003611821575060006105eb565b61074660016118308486612ca2565b61183a9190612c06565b836120e2565b6000806000866001600160a01b03166001600160a01b031681526020019081526020016000206000856001600160a01b03166001600160a01b0316815260200190815260200160002090506000806000806118f77f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000878a8a604051806060016040528060278152602001612dd1602791396120ee565b9350935093509350866001600160601b0316600014158061192057506001600160601b03861615155b1561197a57604080516001600160601b03808a168252881660208201526001600160a01b03808b1692908c16917f739a1b631a38e2bc7ad3fa010c42fb36d61d698dff0f3cdc286c9af4e112651691015b60405180910390a35b81156119da57876001600160a01b0316896001600160a01b03167fa409cf694f828c617da9e6087db63f1d4fa98a83b5729247c40deeb74223f3768360000151846020015187896040516119d19493929190612cdc565b60405180910390a35b505050505050505050565b600060016000856001600160a01b03166001600160a01b031681526020019081526020016000209050600080600080611a7a7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000878a8a6040518060600160405280602b8152602001612df8602b91396120ee565b9350935093509350866001600160601b03166000141580611aa357506001600160601b03861615155b15611af857604080516001600160601b03808a168252881660208201526001600160a01b038a16917f91e2068cc3d2b858a7ac528b7d397c4af810c8de2bec8fb2323b1d3129b6070891015b60405180910390a25b8115611b4b57805160208201516040516001600160a01b038b16927f148a37ac83ecbcaa86f7c2d2e3cdff965fde6aa551bd16cca71c699fcf8b15a792611b429288908a90612cdc565b60405180910390a25b5050505050505050565b6001600160a01b03808516600090815260208181526040808320938716835292905290812090808080611bcb7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000878a8a6122be565b9350935093509350866001600160601b03166000141580611bf457506001600160601b03861615155b1561197a57604080516001600160601b03808a168252881660208201526001600160a01b03808b1692908c16917fd060d8ac6e1d35a3b85f6213429868001fc54dc6df96f7bec00142bca55bb9779101611971565b6001600160a01b038316600090815260016020526040812090808080611cb27f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000878a8a6122be565b9350935093509350866001600160601b03166000141580611cdb57506001600160601b03861615155b15611af857604080516001600160601b03808a168252881660208201526001600160a01b038a16917f3d57595af04c7f20dc620f8ca177326306383045b1a1049016dfda692144102d9101611aef565b6000611d378484611d6e565b909111159392505050565b600063ffffffff808416908516611d5a846001612ca2565b611d649190612d12565b610ae99190612ca2565b600080611d7c848442610f66565b9050610ae9848483612438565b6040805160608101825260008082526020820181905291810191909152826060015161ffff16600003611dd957506040805160608101825260008082526020820181905291810191909152610746565b6000611de58585611462565b8093508192505050816040015163ffffffff168363ffffffff161015611e735761447061ffff16846060015161ffff161015611e44575050604080516060810182526000808252602082015263ffffffff831691810191909152610746565b60408083015190516394a310c560e01b815263ffffffff808616600483015290911660248201526044016113d9565b836060015161ffff16600103611e895750610746565b600080611e968787610ecf565b91509150806040015163ffffffff168563ffffffff1610611ebb579250610746915050565b856060015161ffff16600203611ed357505050610746565b611eec878361ffff168561ffff16888a60600151612453565b6040820151939750919550909350915063ffffffff808716911603611f15579250610746915050565b5050509392505050565b60006001600160601b03821115611f875760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201526536206269747360d01b60648201526084016113d9565b5090565b6001600160a01b03831615801590611fad57506001600160a01b038316600114155b15611fef57611fbf8484600084611840565b6001600160a01b0382161580611fde57506001600160a01b0382166001145b15611fef57611fef846000836119e5565b6001600160a01b0382161580159061201157506001600160a01b038216600114155b15611228576120238483600084611b55565b6001600160a01b038316158061204257506001600160a01b0383166001145b156112285761122884600083611c49565b604080516060810182526000808252602082018190529181019190915260405180606001604052806120af85856040820151602083015192516001600160801b03166001600160601b0390931663ffffffff9190920316020190565b6001600160801b0316815260200184602001516001600160601b031681526020018363ffffffff16815250905092915050565b60006107468284612c43565b604080516060810182526000808252602082018190529181019190915260408051608081018252600080825260208201819052918101829052606081018290528190506040805160808101825288546001600160601b03808216808452600160601b83048216602085015261ffff600160c01b8404811695850195909552600160d01b90920490931660608301529091881611156121a657805160405163e7283f1360e01b81526113d9919089908890600401612d29565b856001600160601b031681602001516001600160601b031610156121e75780602001518686604051630aa41cb360e21b81526004016113d993929190612d29565b6001600160601b0386161580159061220857506122048a8a6114fd565b4211155b81516001600160601b0390899003811683526020830180518990039091169052915081156122445761223c8a8a838b61262f565b919550935090505b805188546020830151604084015160608501516001600160601b039485166001600160c01b031990941693909317600160601b94909216939093021763ffffffff60c01b1916600160c01b61ffff9384160261ffff60d01b191617600160d01b929091169190910217909755919890975090955092505050565b604080516060810182526000808252602082018190529181019190915260408051608081018252600080825260208201819052918101829052606081018290528190506040805160808101825287546001600160601b038082168352600160601b82048116602084015261ffff600160c01b8304811694840194909452600160d01b909104909216606082015290851615801590612365575061236189896114fd565b4211155b915085816000018181516123799190612d95565b6001600160601b0316905250602081018051869190612399908390612d95565b6001600160601b031690525081156123bf576123b78989838a61262f565b919550935090505b805187546020830151604084015160608501516001600160601b039485166001600160c01b031990941693909317600160601b94909216939093021763ffffffff60c01b1916600160c01b61ffff9384160261ffff60d01b191617600160d01b9290911691909102179096559197909650909450915050565b60008263ffffffff168463ffffffff1683611d649190612d12565b6040805160608101825260008082526020820181905291810191909152604080516060810182526000808252602082018190529181018290526000808762ffffff1690506000818a62ffffff16106124b0578962ffffff166124ca565b60016124c061ffff891684612ca2565b6124ca9190612c06565b905060005b60026124db8385612ca2565b6124e59190612c2f565b90506124f5818961ffff166120e2565b95508b8661ffff16614470811061250e5761250e612bda565b6040805160608101825291909201546001600160801b0381168252600160801b81046001600160601b03166020830152600160e01b900463ffffffff1691810182905297506125618261ffff8b166127fa565b94508c8561ffff16614470811061257a5761257a612bda565b6040805160608101825291909201546001600160801b0381168252600160801b81046001600160601b03166020830152600160e01b900463ffffffff9081169282019290925296508a81169082161180159081906125e85750866040015163ffffffff168b63ffffffff1611155b156125f4575050612620565b8061260b57612604600184612c06565b9350612619565b612616836001612ca2565b94505b50506124cf565b50505095509550955095915050565b60408051606081018252600080825260208201819052918101919091526040805160808101825260008082526020820181905291810182905260608101829052600061268163ffffffff881642612c06565b60408051606081018252600080825260208201819052918101829052919250906126b08a8a896001018b61280a565b965090925090508415612709576126cd61ffff83166144706127fa565b61ffff90811660408a015260608901516144709116106126ef576144706126ff565b60608801516126ff906001612db5565b61ffff1660608901525b604051806060016040528061274883866040820151602083015192516001600160801b03166001600160601b0390931663ffffffff9190920316020190565b6001600160801b0316815260200189602001516001600160601b031681526020018463ffffffff16815250955085876001018361ffff16614470811061279057612790612bda565b825191018054602084015160409094015163ffffffff16600160e01b026001600160e01b036001600160601b03909516600160801b026001600160e01b03199092166001600160801b03909416939093171792909216179055509398929750949550909350505050565b600061074661183a846001612ca2565b604080516060810182526000808252602082018190529181018290526000806128338686610ecf565b935090506000612844898942610f66565b9050600061286e8a8a876040015163ffffffff168c63ffffffff166128699190612ca2565b610f66565b9050866060015161ffff166000148061288657508082115b1561289d57505050506040830151915060016128a7565b5090935060009150505b9450945094915050565b6040805160c0810182526000918101828152606082018390526080820183905260a0820192909252908152602081016128e86128ed565b905290565b6040518062088e000160405280614470905b60408051606081018252600080825260208083018290529282015282526000199092019101816128ff5790505090565b80356001600160a01b038116811461294657600080fd5b919050565b60006020828403121561295d57600080fd5b6107468261292f565b80516001600160801b031682526020808201516001600160601b03169083015260409081015163ffffffff16910152565b61ffff83168152608081016107466020830184612966565b6000602082840312156129c157600080fd5b5035919050565b80356001600160601b038116811461294657600080fd5b600080604083850312156129f257600080fd5b6129fb8361292f565b9150612a09602084016129c8565b90509250929050565b600080600060608486031215612a2757600080fd5b612a308461292f565b9250612a3e6020850161292f565b9150604084013590509250925092565b815180516001600160601b0390811683526020808301519091168184015260408083015161ffff908116918501919091526060928301511682840152808401516219aa8084019290916080850160005b614470811015612ac357612ab3828651612966565b9382019390830190600101612a9e565b505050505092915050565b60008060408385031215612ae157600080fd5b612aea8361292f565b9150612a096020840161292f565b60008060408385031215612b0b57600080fd5b612b148361292f565b946020939093013593505050565b60008060008060808587031215612b3857600080fd5b612b418561292f565b9350612b4f6020860161292f565b93969395505050506040820135916060013590565b600080600060608486031215612b7957600080fd5b612b828461292f565b9250612b906020850161292f565b9150612b9e604085016129c8565b90509250925092565b600080600060608486031215612bbc57600080fd5b612bc58461292f565b95602085013595506040909401359392505050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b818103818111156105eb576105eb612bf0565b634e487b7160e01b600052601260045260246000fd5b600082612c3e57612c3e612c19565b500490565b600082612c5257612c52612c19565b500690565b600063ffffffff80841680612c6e57612c6e612c19565b92169190910492915050565b63ffffffff818116838216028082169190828114612c9a57612c9a612bf0565b505092915050565b808201808211156105eb576105eb612bf0565b6001600160801b03828116828216039080821115612cd557612cd5612bf0565b5092915050565b6001600160601b03858116825284166020820152821515604082015260c08101612d096060830184612966565b95945050505050565b80820281158282048414176105eb576105eb612bf0565b60006001600160601b03808616835260208186166020850152606060408501528451915081606085015260005b82811015612d7257858101820151858201608001528101612d56565b50506000608082850101526080601f19601f830116840101915050949350505050565b6001600160601b03818116838216019080821115612cd557612cd5612bf0565b61ffff818116838216019080821115612cd557612cd5612bf056fe54432f6f62736572766174696f6e2d6275726e2d6c742d64656c65676174652d62616c616e636554432f6275726e2d616d6f756e742d657863656564732d746f74616c2d737570706c792d62616c616e6365a2646970667358221220fe54a633b324b431080423450410268e189efccb5a9a0b75eae2e0a1b1758a1c64736f6c634300081800330000000000000000000000000000000000000000000000000000000000000e100000000000000000000000000000000000000000000000000000000066d1ec80

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101c45760003560e01c8063766c4f37116100f9578063be00792911610097578063e7a891b911610071578063e7a891b91461047a578063e7d7b225146104a1578063f7888aec146104b4578063fd590847146104f457600080fd5b8063be00792914610422578063c661667d14610435578063e4dc2aa41461044857600080fd5b80638ab65686116100d35780638ab65686146103be5780638df2c8e6146103d1578063b5f783a8146103e4578063bb35799a1461040f57600080fd5b8063766c4f37146103905780637b2037cd146103a3578063805965f9146103b657600080fd5b80633aaa523211610166578063495b774611610140578063495b7746146102f15780634a5958ba146102f95780634c08d8e81461034157806372a142d01461037d57600080fd5b80633aaa5232146102855780633d594151146102be57806347c6394a146102de57600080fd5b80631fa57c25116101a25780631fa57c2514610229578063224b5c341461024c5780632d9d91f41461025f5780633621b5a91461027257600080fd5b80630b6511f2146101c957806310d14438146101f35780631b025a4014610214575b600080fd5b6101dc6101d736600461294b565b610507565b6040516101ea929190612997565b60405180910390f35b6102066102013660046129af565b61059e565b6040519081526020016101ea565b6102276102223660046129df565b6105f1565b005b61023c6102373660046129af565b610629565b60405190151581526020016101ea565b61020661025a366004612a12565b610676565b61023c61026d3660046129af565b61074d565b6101dc61028036600461294b565b61079a565b61020661029336600461294b565b6001600160a01b0316600090815260016020526040902054600160601b90046001600160601b031690565b6102d16102cc36600461294b565b610827565b6040516101ea9190612a4e565b6101dc6102ec366004612ace565b61091e565b6102066109c4565b610206610307366004612ace565b6001600160a01b0382811660009081526020818152604080832093851683529290522054600160601b90046001600160601b031692915050565b6103687f0000000000000000000000000000000000000000000000000000000066d1ec8081565b60405163ffffffff90911681526020016101ea565b61020661038b3660046129af565b610a15565b61022761039e36600461294b565b610a20565b6102066103b1366004612af8565b610a2f565b610206610af1565b6102276103cc366004612ace565b610b3d565b6102276103df3660046129df565b610b48565b6103f76103f2366004612ace565b610b55565b6040516001600160a01b0390911681526020016101ea565b61020661041d366004612b22565b610b61565b6101dc610430366004612ace565b610c40565b610227610443366004612b64565b610cd9565b61020661045636600461294b565b6001600160a01b03166000908152600160205260409020546001600160601b031690565b6103687f0000000000000000000000000000000000000000000000000000000000000e1081565b6102066104af366004612ba7565b610d11565b6102066104c2366004612ace565b6001600160a01b039182166000908152602081815260408083209390941682529190915220546001600160601b031690565b6102d1610502366004612ace565b610dcb565b604080516060810182526000808252602082018190529181018290526001600160a01b038316600090815260016020818152604092839020835160808101855281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811695820195909552600160d01b909204909316606082015261059491830190610ecf565b9250925050915091565b60006105eb7f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec8084610f66565b92915050565b6001600160a01b03821661061857604051633a954ecd60e21b815260040160405180910390fd5b6106253360008484610fa0565b5050565b60006105eb7f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec808461122e565b6001600160a01b038084166000908152602081815260408083209386168352928152828220835160808101855281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811695820195909552600160d01b9092049093166060820152909190610742907f0000000000000000000000000000000000000000000000000000000000000e10907f0000000000000000000000000000000000000000000000000000000066d1ec8090600185019061073d8861123b565b61139f565b9150505b9392505050565b60006105eb827f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec8061144c565b604080516060810182526000808252602082018190529181018290526001600160a01b038316600090815260016020818152604092839020835160808101855281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811695820195909552600160d01b909204909316606082015261059491830190611462565b61082f6128b1565b6001600160a01b0382166000908152600160208181526040808420815160c08101835281546001600160601b03808216838601908152600160601b8304909116606084015261ffff600160c01b830481166080850152600160d01b90920490911660a08301528152825162088e0081019093529490939285019284019061447090835b828210156109105760408051606081018252858401546001600160801b0381168252600160801b81046001600160601b0316602080840191909152600160e01b90910463ffffffff16928201929092528252600190920191016108b2565b505050915250909392505050565b604080516060810182526000808252602082018190529181018290526001600160a01b03808516600090815260208181526040808320938716835292815290829020825160808101845281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811694820194909452600160d01b90920490921660608201526109b7906001830190610ecf565b92509250505b9250929050565b6000610a107f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec806114fd565b905090565b60006105eb8261123b565b610a2c3382600161152f565b50565b6001600160a01b0382166000908152600160208181526040808420815160808101835281546001600160601b038082168352600160601b8204169482019490945261ffff600160c01b8504811693820193909352600160d01b909304909116606083015291610ae9917f0000000000000000000000000000000000000000000000000000000000000e10917f0000000000000000000000000000000000000000000000000000000066d1ec80919085019061073d8861123b565b949350505050565b6000610a107f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec80611659565b61062582338361152f565b6106253383600084610fa0565b60006107468383611665565b6001600160a01b038085166000908152602081815260408083209387168352928152828220835160808101855281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811695820195909552600160d01b9092049093166060820152909190610c36907f0000000000000000000000000000000000000000000000000000000000000e10907f0000000000000000000000000000000000000000000000000000000066d1ec80906001850190610c288961123b565b610c318961123b565b6116ad565b9695505050505050565b604080516060810182526000808252602082018190529181018290526001600160a01b03808516600090815260208181526040808320938716835292815290829020825160808101845281546001600160601b038082168352600160601b8204169382019390935261ffff600160c01b8404811694820194909452600160d01b90920490921660608201526109b7906001830190611462565b6001600160a01b038216610d0057604051633a954ecd60e21b815260040160405180910390fd5b610d0c33848484610fa0565b505050565b6001600160a01b0383166000908152600160208181526040808420815160808101835281546001600160601b038082168352600160601b8204169482019490945261ffff600160c01b8504811693820193909352600160d01b909304909116606083015291610742917f0000000000000000000000000000000000000000000000000000000000000e10917f0000000000000000000000000000000000000000000000000000000066d1ec809190850190610c288961123b565b610dd36128b1565b6001600160a01b038084166000908152602081815260408083209386168352928152828220835160c08101855281546001600160601b03808216838801908152600160601b8304909116606084015261ffff600160c01b830481166080850152600160d01b90920490911660a08301528152845162088e00810190955293909291840191600184019061447090835b82821015610ec05760408051606081018252858401546001600160801b0381168252600160801b81046001600160601b0316602080840191909152600160e01b90910463ffffffff1692820192909252825260019092019101610e62565b50505091525090949350505050565b604080516060810182526000808252602082018190529181018290526040830151610f009061ffff1661447061180f565b9150838261ffff166144708110610f1957610f19612bda565b6040805160608101825292909101546001600160801b03811683526001600160601b03600160801b820416602084015263ffffffff600160e01b9091041690820152919491935090915050565b60008263ffffffff168211610f7d57506000610746565b8363ffffffff168363ffffffff1683610f969190612c06565b610ae99190612c2f565b6000196001600160a01b03831601610fcb57604051635bb7132160e11b815260040160405180910390fd5b816001600160a01b0316836001600160a01b03160315611228576000610ff18585611665565b90506000610fff8685611665565b90506001600160a01b03851615611113576001600160a01b038281169086161461103787878684611031576000611840565b87611840565b8015801561104f57506001600160a01b038316600114155b15611061576110618784600087611840565b6001600160a01b038516158061109557506001600160a01b038216600114801561109557506001600160a01b038316600114155b1561111157611111876001600160a01b038716156110b45760006110b6565b855b6001600160a01b0388161580156110d757506001600160a01b038616600114155b8061110057506001600160a01b038516600114801561110057506001600160a01b038616600114155b61110b5760006119e5565b866119e5565b505b6001600160a01b03841615611225576001600160a01b038181169085161461114987868684611143576000611b55565b87611b55565b8015801561116157506001600160a01b038216600114155b15611173576111738783600087611b55565b6001600160a01b03861615806111a757506001600160a01b03831660011480156111a757506001600160a01b038216600114155b1561122357611223876001600160a01b038816156111c65760006111c8565b855b6001600160a01b0389161580156111e957506001600160a01b038516600114155b8061121257506001600160a01b038616600114801561121257506001600160a01b038516600114155b61121d576000611c49565b86611c49565b505b50505b50505050565b6000610ae9848484611d2b565b60007f0000000000000000000000000000000000000000000000000000000066d1ec8063ffffffff1682101561129757505063ffffffff7f0000000000000000000000000000000000000000000000000000000066d1ec801690565b7f0000000000000000000000000000000000000000000000000000000000000e1063ffffffff167f0000000000000000000000000000000000000000000000000000000066d1ec8063ffffffff16836112f09190612c06565b6112fa9190612c43565b600003611305575090565b60006113527f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec8085610f66565b90506107467f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec8083611d42565b600085858360006113b08484611d6e565b9050808211156113e25760405163947ad91360e01b815260048101839052602481018290526044015b60405180910390fd5b8863ffffffff168610156113f9576000945061143f565b611404868b8b61144c565b15611412576000945061143f565b600061142e898961142963ffffffff8e168b612c06565b611d89565b602001516001600160601b03169550505b5050505095945050505050565b600061145883836114fd565b9093119392505050565b6040805160608101825260008082526020820181905291810182905261447061ffff16836060015161ffff1610156114df5750506040805160608101825283546001600160801b0381168252600160801b81046001600160601b03166020830152600160e01b900463ffffffff16918101919091526000906109bd565b82604001519150838261ffff166144708110610f1957610f19612bda565b60008261150e8163ffffffff612c57565b6115189190612c7a565b63ffffffff168263ffffffff166107469190612ca2565b600061153b8484611665565b905060006001600160a01b038316156115545782611557565b60015b9050816001600160a01b0316816001600160a01b03160361159657604051634929aa6560e01b81526001600160a01b03821660048201526024016113d9565b6001600160a01b03858116600081815260026020908152604080832089861680855290835281842080546001600160a01b031916968816969096179095559282528181528282209382529290925290205461160890869084908690611603906001600160601b0316611f1f565b611f8b565b806001600160a01b0316846001600160a01b0316866001600160a01b03167f2190b8902ea4a5dbea665e1965f2b2c0b04788c8831da4d881b56ddc9ead4fe860405160405180910390a45050505050565b60006107468383611d6e565b6000806001600160a01b0383161561074657506001600160a01b0380841660009081526002602090815260408083208685168452909152902054168061074657509092915050565b600086868360006116be8484611d6e565b9050808211156116eb5760405163947ad91360e01b815260048101839052602481018290526044016113d9565b8686101561171657604051630e781b2360e31b815260048101889052602481018790526044016113d9565b611721868c8c61144c565b1561172f5760009450611801565b600061174163ffffffff8c1689612c06565b9050600061175563ffffffff8d1689612c06565b905060006117648c8c84611d89565b905081830361178457602001516001600160601b03169650611801915050565b60006117918d8d86611d89565b905083816040015163ffffffff16146117b1576117ae8185612053565b90505b82826040015163ffffffff16146117cf576117cc8284612053565b91505b6117d98484612c06565b815183516117e79190612cb5565b6001600160801b03166117fa9190612c2f565b9850505050505b505050509695505050505050565b600081600003611821575060006105eb565b61074660016118308486612ca2565b61183a9190612c06565b836120e2565b6000806000866001600160a01b03166001600160a01b031681526020019081526020016000206000856001600160a01b03166001600160a01b0316815260200190815260200160002090506000806000806118f77f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec80878a8a604051806060016040528060278152602001612dd1602791396120ee565b9350935093509350866001600160601b0316600014158061192057506001600160601b03861615155b1561197a57604080516001600160601b03808a168252881660208201526001600160a01b03808b1692908c16917f739a1b631a38e2bc7ad3fa010c42fb36d61d698dff0f3cdc286c9af4e112651691015b60405180910390a35b81156119da57876001600160a01b0316896001600160a01b03167fa409cf694f828c617da9e6087db63f1d4fa98a83b5729247c40deeb74223f3768360000151846020015187896040516119d19493929190612cdc565b60405180910390a35b505050505050505050565b600060016000856001600160a01b03166001600160a01b031681526020019081526020016000209050600080600080611a7a7f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec80878a8a6040518060600160405280602b8152602001612df8602b91396120ee565b9350935093509350866001600160601b03166000141580611aa357506001600160601b03861615155b15611af857604080516001600160601b03808a168252881660208201526001600160a01b038a16917f91e2068cc3d2b858a7ac528b7d397c4af810c8de2bec8fb2323b1d3129b6070891015b60405180910390a25b8115611b4b57805160208201516040516001600160a01b038b16927f148a37ac83ecbcaa86f7c2d2e3cdff965fde6aa551bd16cca71c699fcf8b15a792611b429288908a90612cdc565b60405180910390a25b5050505050505050565b6001600160a01b03808516600090815260208181526040808320938716835292905290812090808080611bcb7f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec80878a8a6122be565b9350935093509350866001600160601b03166000141580611bf457506001600160601b03861615155b1561197a57604080516001600160601b03808a168252881660208201526001600160a01b03808b1692908c16917fd060d8ac6e1d35a3b85f6213429868001fc54dc6df96f7bec00142bca55bb9779101611971565b6001600160a01b038316600090815260016020526040812090808080611cb27f0000000000000000000000000000000000000000000000000000000000000e107f0000000000000000000000000000000000000000000000000000000066d1ec80878a8a6122be565b9350935093509350866001600160601b03166000141580611cdb57506001600160601b03861615155b15611af857604080516001600160601b03808a168252881660208201526001600160a01b038a16917f3d57595af04c7f20dc620f8ca177326306383045b1a1049016dfda692144102d9101611aef565b6000611d378484611d6e565b909111159392505050565b600063ffffffff808416908516611d5a846001612ca2565b611d649190612d12565b610ae99190612ca2565b600080611d7c848442610f66565b9050610ae9848483612438565b6040805160608101825260008082526020820181905291810191909152826060015161ffff16600003611dd957506040805160608101825260008082526020820181905291810191909152610746565b6000611de58585611462565b8093508192505050816040015163ffffffff168363ffffffff161015611e735761447061ffff16846060015161ffff161015611e44575050604080516060810182526000808252602082015263ffffffff831691810191909152610746565b60408083015190516394a310c560e01b815263ffffffff808616600483015290911660248201526044016113d9565b836060015161ffff16600103611e895750610746565b600080611e968787610ecf565b91509150806040015163ffffffff168563ffffffff1610611ebb579250610746915050565b856060015161ffff16600203611ed357505050610746565b611eec878361ffff168561ffff16888a60600151612453565b6040820151939750919550909350915063ffffffff808716911603611f15579250610746915050565b5050509392505050565b60006001600160601b03821115611f875760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201526536206269747360d01b60648201526084016113d9565b5090565b6001600160a01b03831615801590611fad57506001600160a01b038316600114155b15611fef57611fbf8484600084611840565b6001600160a01b0382161580611fde57506001600160a01b0382166001145b15611fef57611fef846000836119e5565b6001600160a01b0382161580159061201157506001600160a01b038216600114155b15611228576120238483600084611b55565b6001600160a01b038316158061204257506001600160a01b0383166001145b156112285761122884600083611c49565b604080516060810182526000808252602082018190529181019190915260405180606001604052806120af85856040820151602083015192516001600160801b03166001600160601b0390931663ffffffff9190920316020190565b6001600160801b0316815260200184602001516001600160601b031681526020018363ffffffff16815250905092915050565b60006107468284612c43565b604080516060810182526000808252602082018190529181019190915260408051608081018252600080825260208201819052918101829052606081018290528190506040805160808101825288546001600160601b03808216808452600160601b83048216602085015261ffff600160c01b8404811695850195909552600160d01b90920490931660608301529091881611156121a657805160405163e7283f1360e01b81526113d9919089908890600401612d29565b856001600160601b031681602001516001600160601b031610156121e75780602001518686604051630aa41cb360e21b81526004016113d993929190612d29565b6001600160601b0386161580159061220857506122048a8a6114fd565b4211155b81516001600160601b0390899003811683526020830180518990039091169052915081156122445761223c8a8a838b61262f565b919550935090505b805188546020830151604084015160608501516001600160601b039485166001600160c01b031990941693909317600160601b94909216939093021763ffffffff60c01b1916600160c01b61ffff9384160261ffff60d01b191617600160d01b929091169190910217909755919890975090955092505050565b604080516060810182526000808252602082018190529181019190915260408051608081018252600080825260208201819052918101829052606081018290528190506040805160808101825287546001600160601b038082168352600160601b82048116602084015261ffff600160c01b8304811694840194909452600160d01b909104909216606082015290851615801590612365575061236189896114fd565b4211155b915085816000018181516123799190612d95565b6001600160601b0316905250602081018051869190612399908390612d95565b6001600160601b031690525081156123bf576123b78989838a61262f565b919550935090505b805187546020830151604084015160608501516001600160601b039485166001600160c01b031990941693909317600160601b94909216939093021763ffffffff60c01b1916600160c01b61ffff9384160261ffff60d01b191617600160d01b9290911691909102179096559197909650909450915050565b60008263ffffffff168463ffffffff1683611d649190612d12565b6040805160608101825260008082526020820181905291810191909152604080516060810182526000808252602082018190529181018290526000808762ffffff1690506000818a62ffffff16106124b0578962ffffff166124ca565b60016124c061ffff891684612ca2565b6124ca9190612c06565b905060005b60026124db8385612ca2565b6124e59190612c2f565b90506124f5818961ffff166120e2565b95508b8661ffff16614470811061250e5761250e612bda565b6040805160608101825291909201546001600160801b0381168252600160801b81046001600160601b03166020830152600160e01b900463ffffffff1691810182905297506125618261ffff8b166127fa565b94508c8561ffff16614470811061257a5761257a612bda565b6040805160608101825291909201546001600160801b0381168252600160801b81046001600160601b03166020830152600160e01b900463ffffffff9081169282019290925296508a81169082161180159081906125e85750866040015163ffffffff168b63ffffffff1611155b156125f4575050612620565b8061260b57612604600184612c06565b9350612619565b612616836001612ca2565b94505b50506124cf565b50505095509550955095915050565b60408051606081018252600080825260208201819052918101919091526040805160808101825260008082526020820181905291810182905260608101829052600061268163ffffffff881642612c06565b60408051606081018252600080825260208201819052918101829052919250906126b08a8a896001018b61280a565b965090925090508415612709576126cd61ffff83166144706127fa565b61ffff90811660408a015260608901516144709116106126ef576144706126ff565b60608801516126ff906001612db5565b61ffff1660608901525b604051806060016040528061274883866040820151602083015192516001600160801b03166001600160601b0390931663ffffffff9190920316020190565b6001600160801b0316815260200189602001516001600160601b031681526020018463ffffffff16815250955085876001018361ffff16614470811061279057612790612bda565b825191018054602084015160409094015163ffffffff16600160e01b026001600160e01b036001600160601b03909516600160801b026001600160e01b03199092166001600160801b03909416939093171792909216179055509398929750949550909350505050565b600061074661183a846001612ca2565b604080516060810182526000808252602082018190529181018290526000806128338686610ecf565b935090506000612844898942610f66565b9050600061286e8a8a876040015163ffffffff168c63ffffffff166128699190612ca2565b610f66565b9050866060015161ffff166000148061288657508082115b1561289d57505050506040830151915060016128a7565b5090935060009150505b9450945094915050565b6040805160c0810182526000918101828152606082018390526080820183905260a0820192909252908152602081016128e86128ed565b905290565b6040518062088e000160405280614470905b60408051606081018252600080825260208083018290529282015282526000199092019101816128ff5790505090565b80356001600160a01b038116811461294657600080fd5b919050565b60006020828403121561295d57600080fd5b6107468261292f565b80516001600160801b031682526020808201516001600160601b03169083015260409081015163ffffffff16910152565b61ffff83168152608081016107466020830184612966565b6000602082840312156129c157600080fd5b5035919050565b80356001600160601b038116811461294657600080fd5b600080604083850312156129f257600080fd5b6129fb8361292f565b9150612a09602084016129c8565b90509250929050565b600080600060608486031215612a2757600080fd5b612a308461292f565b9250612a3e6020850161292f565b9150604084013590509250925092565b815180516001600160601b0390811683526020808301519091168184015260408083015161ffff908116918501919091526060928301511682840152808401516219aa8084019290916080850160005b614470811015612ac357612ab3828651612966565b9382019390830190600101612a9e565b505050505092915050565b60008060408385031215612ae157600080fd5b612aea8361292f565b9150612a096020840161292f565b60008060408385031215612b0b57600080fd5b612b148361292f565b946020939093013593505050565b60008060008060808587031215612b3857600080fd5b612b418561292f565b9350612b4f6020860161292f565b93969395505050506040820135916060013590565b600080600060608486031215612b7957600080fd5b612b828461292f565b9250612b906020850161292f565b9150612b9e604085016129c8565b90509250925092565b600080600060608486031215612bbc57600080fd5b612bc58461292f565b95602085013595506040909401359392505050565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b818103818111156105eb576105eb612bf0565b634e487b7160e01b600052601260045260246000fd5b600082612c3e57612c3e612c19565b500490565b600082612c5257612c52612c19565b500690565b600063ffffffff80841680612c6e57612c6e612c19565b92169190910492915050565b63ffffffff818116838216028082169190828114612c9a57612c9a612bf0565b505092915050565b808201808211156105eb576105eb612bf0565b6001600160801b03828116828216039080821115612cd557612cd5612bf0565b5092915050565b6001600160601b03858116825284166020820152821515604082015260c08101612d096060830184612966565b95945050505050565b80820281158282048414176105eb576105eb612bf0565b60006001600160601b03808616835260208186166020850152606060408501528451915081606085015260005b82811015612d7257858101820151858201608001528101612d56565b50506000608082850101526080601f19601f830116840101915050949350505050565b6001600160601b03818116838216019080821115612cd557612cd5612bf0565b61ffff818116838216019080821115612cd557612cd5612bf056fe54432f6f62736572766174696f6e2d6275726e2d6c742d64656c65676174652d62616c616e636554432f6275726e2d616d6f756e742d657863656564732d746f74616c2d737570706c792d62616c616e6365a2646970667358221220fe54a633b324b431080423450410268e189efccb5a9a0b75eae2e0a1b1758a1c64736f6c63430008180033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000000000000000000000000000000000000000000e100000000000000000000000000000000000000000000000000000000066d1ec80

-----Decoded View---------------
Arg [0] : _periodLength (uint32): 3600
Arg [1] : _periodOffset (uint32): 1725033600

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000e10
Arg [1] : 0000000000000000000000000000000000000000000000000000000066d1ec80


Block Transaction Gas Used Reward
view all blocks sequenced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
0xB0e5bc69065eF1078fd641aE6A0860441e9e21e4
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.