Skip to main content

TwabLib

Git Source

Author: PoolTogether Inc. & G9 Software Inc.

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.

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.

Time-Weighted Average Balance Library for ERC20 tokens.

Types

PeriodOffsetRelativeTimestamp

type PeriodOffsetRelativeTimestamp is uint32;

Functions

increaseBalances

Increase a user's balance and delegate balance by a given amount.

This function mutates the provided account.

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

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The length of an overwrite period
PERIOD_OFFSETuint32The offset of the first period
_accountAccountThe account to update
_amountuint96The amount to increase the balance by
_delegateAmountuint96The amount to increase the delegate balance by

Returns

NameTypeDescription
observationObservationLib.ObservationThe new/updated observation
isNewboolWhether or not the observation is new or overwrote a previous one
isObservationRecordedboolWhether or not an observation was recorded to storage
accountDetailsAccountDetails

decreaseBalances

Decrease a user's balance and delegate balance by a given amount.

This function mutates the provided account.

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

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The length of an overwrite period
PERIOD_OFFSETuint32The offset of the first period
_accountAccountThe account to update
_amountuint96The amount to decrease the balance by
_delegateAmountuint96The amount to decrease the delegate balance by
_revertMessagestringThe revert message to use if the balance is insufficient

Returns

NameTypeDescription
observationObservationLib.ObservationThe new/updated observation
isNewboolWhether or not the observation is new or overwrote a previous one
isObservationRecordedboolWhether or not the observation was recorded to storage
accountDetailsAccountDetails

getOldestObservation

Looks up 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);

Parameters

NameTypeDescription
_observationsObservationLib.Observation[MAX_CARDINALITY]The circular buffer of observations
_accountDetailsAccountDetailsThe account details to query with

Returns

NameTypeDescription
indexuint16The index of the oldest observation
observationObservationLib.ObservationThe oldest observation in the circular buffer

getNewestObservation

Looks up 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);

Parameters

NameTypeDescription
_observationsObservationLib.Observation[MAX_CARDINALITY]The circular buffer of observations
_accountDetailsAccountDetailsThe account details to query with

Returns

NameTypeDescription
indexuint16The index of the newest observation
observationObservationLib.ObservationThe newest observation in the circular buffer

getBalanceAt

Looks up a users balance at a specific time in the past. The time must be before the current overwrite period.

Ensure timestamps are safe using requireFinalized

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

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The length of an overwrite period
PERIOD_OFFSETuint32The offset of the first period
_observationsObservationLib.Observation[MAX_CARDINALITY]The circular buffer of observations
_accountDetailsAccountDetailsThe account details to query with
_targetTimeuint256The time to look up the balance at

Returns

NameTypeDescription
<none>uint256balance The balance at the target time

isShutdownAt

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.

function isShutdownAt(uint256 timestamp, uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET) internal pure returns (bool);

Parameters

NameTypeDescription
timestampuint256The timestamp to check
PERIOD_LENGTHuint32
PERIOD_OFFSETuint32The offset of the first period

Returns

NameTypeDescription
<none>boolTrue if the TwabController is shutdown at the given timestamp, false otherwise.

lastObservationAt

Computes the largest timestamp at which the TwabController can record a new observation.

function lastObservationAt(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET) internal pure returns (uint256);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32
PERIOD_OFFSETuint32The offset of the first period

Returns

NameTypeDescription
<none>uint256The largest timestamp at which the TwabController can record a new observation.

getTwabBetween

Looks up a users TWAB for a time range. The time must be before the current overwrite period.

If the timestamps in the range are not exact matches of observations, the balance is extrapolated using the previous observation.

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

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The length of an overwrite period
PERIOD_OFFSETuint32The offset of the first period
_observationsObservationLib.Observation[MAX_CARDINALITY]The circular buffer of observations
_accountDetailsAccountDetailsThe account details to query with
_startTimeuint256The start of the time range
_endTimeuint256The end of the time range

Returns

NameTypeDescription
<none>uint256twab The TWAB for the time range

_recordObservation

Given an AccountDetails with updated balances, either updates the latest Observation or records a new one

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

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The overwrite period length
PERIOD_OFFSETuint32The overwrite period offset
_accountDetailsAccountDetailsThe updated account details
_accountAccountThe account to update

Returns

NameTypeDescription
observationObservationLib.ObservationThe new/updated observation
isNewboolWhether or not the observation is new or overwrote a previous one
newAccountDetailsAccountDetailsThe new account details

_calculateTemporaryObservation

Calculates a temporary observation for a given time using the previous observation.

This is used to extrapolate a balance for any given time.

function _calculateTemporaryObservation(
ObservationLib.Observation memory _observation,
PeriodOffsetRelativeTimestamp _time
) private pure returns (ObservationLib.Observation memory);

Parameters

NameTypeDescription
_observationObservationLib.ObservationThe previous observation
_timePeriodOffsetRelativeTimestampThe time to extrapolate to

_getNextObservationIndex

Looks up the next observation index to write to in the circular buffer.

If the current time is in the same period as the newest observation, we overwrite it.

If the current time is in a new period, we increment the index and write a new observation.

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

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The length of an overwrite period
PERIOD_OFFSETuint32The offset of the first period
_observationsObservationLib.Observation[MAX_CARDINALITY]The circular buffer of observations
_accountDetailsAccountDetailsThe account details to query with

Returns

NameTypeDescription
indexuint16The index of the next observation slot to overwrite
newestObservationObservationLib.ObservationThe newest observation in the circular buffer
isNewboolTrue if the observation slot is new, false if we're overwriting

_currentOverwritePeriodStartedAt

Computes the start time of the current overwrite period

function _currentOverwritePeriodStartedAt(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET) private view returns (uint256);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The length of an overwrite period
PERIOD_OFFSETuint32The offset of the first period

Returns

NameTypeDescription
<none>uint256The start time of the current overwrite period

_extrapolateFromBalance

Calculates the next cumulative balance using a provided Observation and timestamp.

function _extrapolateFromBalance(
ObservationLib.Observation memory _observation,
PeriodOffsetRelativeTimestamp _offsetTimestamp
) private pure returns (uint128);

Parameters

NameTypeDescription
_observationObservationLib.ObservationThe observation to extrapolate from
_offsetTimestampPeriodOffsetRelativeTimestampThe timestamp to extrapolate to

Returns

NameTypeDescription
<none>uint128cumulativeBalance The cumulative balance at the timestamp

currentOverwritePeriodStartedAt

Computes the overwrite period start time given the current time

function currentOverwritePeriodStartedAt(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET) internal view returns (uint256);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The length of an overwrite period
PERIOD_OFFSETuint32The offset of the first period

Returns

NameTypeDescription
<none>uint256The start time for the current overwrite period.

getTimestampPeriod

Calculates the period a timestamp falls within.

Timestamp prior to the PERIOD_OFFSET are considered to be in period 0.

function getTimestampPeriod(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET, uint256 _timestamp)
internal
pure
returns (uint256);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The length of an overwrite period
PERIOD_OFFSETuint32The offset of the first period
_timestampuint256The timestamp to calculate the period for

Returns

NameTypeDescription
<none>uint256period The period

getPeriodStartTime

Calculates the start timestamp for a period

function getPeriodStartTime(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET, uint256 _period)
internal
pure
returns (uint256);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The period length to use to calculate the period
PERIOD_OFFSETuint32The period offset to use to calculate the period
_perioduint256The period to check

Returns

NameTypeDescription
<none>uint256_timestamp The timestamp at which the period starts

getPeriodEndTime

Calculates the last timestamp for a period

function getPeriodEndTime(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET, uint256 _period)
internal
pure
returns (uint256);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The period length to use to calculate the period
PERIOD_OFFSETuint32The period offset to use to calculate the period
_perioduint256The period to check

Returns

NameTypeDescription
<none>uint256_timestamp The timestamp at which the period ends

getPreviousOrAtObservation

Looks up the newest observation before or at a given timestamp.

If an observation is available at the target time, it is returned. Otherwise, the newest observation before the target time is returned.

function getPreviousOrAtObservation(
uint32 PERIOD_OFFSET,
ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
AccountDetails memory _accountDetails,
uint256 _targetTime
) internal view returns (ObservationLib.Observation memory prevOrAtObservation);

Parameters

NameTypeDescription
PERIOD_OFFSETuint32The period offset to use to calculate the period
_observationsObservationLib.Observation[MAX_CARDINALITY]The circular buffer of observations
_accountDetailsAccountDetailsThe account details to query with
_targetTimeuint256The timestamp to look up

Returns

NameTypeDescription
prevOrAtObservationObservationLib.ObservationThe observation

_getPreviousOrAtObservation

Looks up the newest observation before or at a given timestamp.

If an observation is available at the target time, it is returned. Otherwise, the newest observation before the target time is returned.

function _getPreviousOrAtObservation(
ObservationLib.Observation[MAX_CARDINALITY] storage _observations,
AccountDetails memory _accountDetails,
PeriodOffsetRelativeTimestamp _offsetTargetTime
) private view returns (ObservationLib.Observation memory prevOrAtObservation);

Parameters

NameTypeDescription
_observationsObservationLib.Observation[MAX_CARDINALITY]The circular buffer of observations
_accountDetailsAccountDetailsThe account details to query with
_offsetTargetTimePeriodOffsetRelativeTimestampThe timestamp to look up (offset by the period offset)

Returns

NameTypeDescription
prevOrAtObservationObservationLib.ObservationThe observation

hasFinalized

Checks if the given timestamp is safe to perform a historic balance lookup on.

A timestamp is safe if it is before the current overwrite period

function hasFinalized(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET, uint256 _time) internal view returns (bool);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The period length to use to calculate the period
PERIOD_OFFSETuint32The period offset to use to calculate the period
_timeuint256The timestamp to check

Returns

NameTypeDescription
<none>boolisSafe Whether or not the timestamp is safe

_hasFinalized

Checks if the given timestamp is safe to perform a historic balance lookup on.

A timestamp is safe if it is on or before the current overwrite period start time

function _hasFinalized(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET, uint256 _time) private view returns (bool);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The period length to use to calculate the period
PERIOD_OFFSETuint32The period offset to use to calculate the period
_timeuint256The timestamp to check

Returns

NameTypeDescription
<none>boolisSafe Whether or not the timestamp is safe

requireFinalized

Checks if the given timestamp is safe to perform a historic balance lookup on.

modifier requireFinalized(uint32 PERIOD_LENGTH, uint32 PERIOD_OFFSET, uint256 _timestamp);

Parameters

NameTypeDescription
PERIOD_LENGTHuint32The period length to use to calculate the period
PERIOD_OFFSETuint32The period offset to use to calculate the period
_timestampuint256The timestamp to check

Structs

AccountDetails

Struct ring buffer parameters for single user Account.

struct AccountDetails {
uint96 balance;
uint96 delegateBalance;
uint16 nextObservationIndex;
uint16 cardinality;
}

Properties

NameTypeDescription
balanceuint96Current token balance for an Account
delegateBalanceuint96Current delegate balance for an Account (active balance for chance)
nextObservationIndexuint16Next uninitialized or updatable ring buffer checkpoint storage slot
cardinalityuint16Current total "initialized" ring buffer checkpoints for single user Account. Used to set initial boundary conditions for an efficient binary search.

Account

Account details and historical twabs.

The size of observations is MAX_CARDINALITY from the ObservationLib.

struct Account {
AccountDetails details;
ObservationLib.Observation[17520] observations;
}

Properties

NameTypeDescription
detailsAccountDetailsThe account details
observationsObservationLib.Observation[17520]The history of observations for this account