Skip to main content

TwabRewards

Git Source

Inherits: ITwabRewards, Multicall

Author: PoolTogether Inc. & G9 Software Inc.

Contract to distribute rewards to depositors in a PoolTogether V5 Vault. This contract supports the creation of several promotions that can run simultaneously. In order to calculate user rewards, we use the TWAB (Time-Weighted Average Balance) for the vault and depositor. This way, users simply need to hold their vault tokens to be eligible to claim rewards. Rewards are calculated based on the average amount of vault tokens they hold during the epoch duration.

This contract does not support the use of fee on transfer tokens.

State Variables

twabController

TwabController contract from which the promotions read time-weighted average balances from.

TwabController public immutable twabController;

GRACE_PERIOD

Period during which the promotion owner can't destroy a promotion.

uint32 public constant GRACE_PERIOD = 60 days;

_promotions

Settings of each promotion.

mapping(uint256 => Promotion) internal _promotions;

_latestPromotionId

Latest recorded promotion id.

Starts at 0 and is incremented by 1 for each new promotion. So the first promotion will have id 1, the second 2, etc.

uint256 internal _latestPromotionId;

_claimedEpochs

Keeps track of claimed rewards per user.

_claimedEpochs[promotionId][user] => claimedEpochs

We pack epochs claimed by a user into a uint256. So we can't store more than 256 epochs.

mapping(uint256 => mapping(address => uint256)) internal _claimedEpochs;

Functions

constructor

Constructor of the contract.

constructor(TwabController _twabController);

Parameters

NameTypeDescription
_twabControllerTwabControllerThe TwabController contract to reference for vault balance and supply

createPromotion

Creates a new promotion.

For sake of simplicity, msg.sender will be the creator of the promotion.

_latestPromotionId starts at 0 and is incremented by 1 for each new promotion. So the first promotion will have id 1, the second 2, etc.

The transaction will revert if the amount of reward tokens provided is not equal to `_tokensPerEpoch _numberOfEpochs`. This scenario could happen if the token supplied is a fee on transfer one.*

function createPromotion(
address _vault,
IERC20 _token,
uint64 _startTimestamp,
uint256 _tokensPerEpoch,
uint48 _epochDuration,
uint8 _numberOfEpochs
) external override returns (uint256);

Parameters

NameTypeDescription
_vaultaddress
_tokenIERC20
_startTimestampuint64
_tokensPerEpochuint256
_epochDurationuint48
_numberOfEpochsuint8

Returns

NameTypeDescription
<none>uint256Id of the newly created promotion

endPromotion

End currently active promotion and send promotion tokens back to the creator.

Will only send back tokens from the epochs that have not completed.

function endPromotion(uint256 _promotionId, address _to) external override returns (bool);

Parameters

NameTypeDescription
_promotionIduint256
_toaddress

Returns

NameTypeDescription
<none>boolTrue if operation was successful

destroyPromotion

Delete an inactive promotion and send promotion tokens back to the creator.

Will send back all the tokens that have not been claimed yet by users.

function destroyPromotion(uint256 _promotionId, address _to) external override returns (bool);

Parameters

NameTypeDescription
_promotionIduint256
_toaddress

Returns

NameTypeDescription
<none>boolTrue if operation was successful

extendPromotion

Extend promotion by adding more epochs.

function extendPromotion(uint256 _promotionId, uint8 _numberOfEpochs) external override returns (bool);

Parameters

NameTypeDescription
_promotionIduint256
_numberOfEpochsuint8

Returns

NameTypeDescription
<none>boolTrue if the operation was successful

claimRewards

Claim rewards for a given promotion and epoch.

Rewards can be claimed on behalf of a user.

function claimRewards(address _user, uint256 _promotionId, uint8[] calldata _epochIds)
external
override
returns (uint256);

Parameters

NameTypeDescription
_useraddress
_promotionIduint256
_epochIdsuint8[]

Returns

NameTypeDescription
<none>uint256Total amount of rewards claimed

getPromotion

Get settings for a specific promotion.

function getPromotion(uint256 _promotionId) external view override returns (Promotion memory);

Parameters

NameTypeDescription
_promotionIduint256

Returns

NameTypeDescription
<none>PromotionPromotion settings

getCurrentEpochId

Get the current epoch id of a promotion.

Epoch ids and their boolean values are tightly packed and stored in a uint256, so epoch id starts at 0.

function getCurrentEpochId(uint256 _promotionId) external view override returns (uint256);

Parameters

NameTypeDescription
_promotionIduint256

Returns

NameTypeDescription
<none>uint256Current epoch id of the promotion

getRemainingRewards

Get the total amount of tokens left to be rewarded.

function getRemainingRewards(uint256 _promotionId) external view override returns (uint256);

Parameters

NameTypeDescription
_promotionIduint256

Returns

NameTypeDescription
<none>uint256Amount of tokens left to be rewarded

getRewardsAmount

Get amount of tokens to be rewarded for a given epoch.

Rewards amount can only be retrieved for epochs that are over.

function getRewardsAmount(address _user, uint256 _promotionId, uint8[] calldata _epochIds)
external
view
override
returns (uint256[] memory);

Parameters

NameTypeDescription
_useraddress
_promotionIduint256
_epochIdsuint8[]

Returns

NameTypeDescription
<none>uint256[]Amount of tokens per epoch to be rewarded

_requireNumberOfEpochs

Allow a promotion to be created or extended only by a positive number of epochs.

function _requireNumberOfEpochs(uint8 _numberOfEpochs) internal pure;

Parameters

NameTypeDescription
_numberOfEpochsuint8Number of epochs to check

_requirePromotionActive

Requires that a promotion is active.

function _requirePromotionActive(uint256 _promotionId, Promotion memory _promotion) internal view;

Parameters

NameTypeDescription
_promotionIduint256
_promotionPromotionPromotion to check

_requirePromotionCreator

Requires that msg.sender is the promotion creator.

function _requirePromotionCreator(Promotion memory _promotion) internal view;

Parameters

NameTypeDescription
_promotionPromotionPromotion to check

_getPromotion

Get settings for a specific promotion.

Will revert if the promotion does not exist.

function _getPromotion(uint256 _promotionId) internal view returns (Promotion memory);

Parameters

NameTypeDescription
_promotionIduint256Promotion id to get settings for

Returns

NameTypeDescription
<none>PromotionPromotion settings

_getPromotionEndTimestamp

Compute promotion end timestamp.

function _getPromotionEndTimestamp(Promotion memory _promotion) internal pure returns (uint256);

Parameters

NameTypeDescription
_promotionPromotionPromotion to compute end timestamp for

Returns

NameTypeDescription
<none>uint256Promotion end timestamp

_getCurrentEpochId

Get the current epoch id of a promotion.

Epoch ids and their boolean values are tightly packed and stored in a uint256, so epoch id starts at 0.

We return the current epoch id if the promotion has not ended. If the current timestamp is before the promotion start timestamp, we return 0. Otherwise, we return the epoch id at the current timestamp. This could be greater than the number of epochs of the promotion.

function _getCurrentEpochId(Promotion memory _promotion) internal view returns (uint256);

Parameters

NameTypeDescription
_promotionPromotionPromotion to get current epoch for

Returns

NameTypeDescription
<none>uint256Epoch id

_calculateRewardAmount

Get reward amount for a specific user.

Rewards can only be calculated once the epoch is over.

Will revert if _epochId is over the total number of epochs or if epoch is not over.

Will return 0 if the user average balance in the vault is 0.

function _calculateRewardAmount(address _user, Promotion memory _promotion, uint8 _epochId)
internal
view
returns (uint256);

Parameters

NameTypeDescription
_useraddressUser to get reward amount for
_promotionPromotionPromotion from which the epoch is
_epochIduint8Epoch id to get reward amount for

Returns

NameTypeDescription
<none>uint256Reward amount

_getRemainingRewards

Get the total amount of tokens left to be rewarded.

function _getRemainingRewards(Promotion memory _promotion) internal view returns (uint256);

Parameters

NameTypeDescription
_promotionPromotionPromotion to get the total amount of tokens left to be rewarded for

Returns

NameTypeDescription
<none>uint256Amount of tokens left to be rewarded

_updateClaimedEpoch

Set boolean value for a specific epoch.

Bits are stored in a uint256 from right to left. Let's take the example of the following 8 bits word. 0110 0011 To set the boolean value to 1 for the epoch id 2, we need to create a mask by shifting 1 to the left by 2 bits. We get: 0000 0001 << 2 = 0000 0100 We then OR the mask with the word to set the value. We get: 0110 0011 | 0000 0100 = 0110 0111

function _updateClaimedEpoch(uint256 _userClaimedEpochs, uint8 _epochId) internal pure returns (uint256);

Parameters

NameTypeDescription
_userClaimedEpochsuint256Tightly packed epoch ids with their boolean values
_epochIduint8Id of the epoch to set the boolean for

Returns

NameTypeDescription
<none>uint256Tightly packed epoch ids with the newly boolean value set

_isClaimedEpoch

Check if rewards of an epoch for a given promotion have already been claimed by the user.

Bits are stored in a uint256 from right to left. Let's take the example of the following 8 bits word. 0110 0111 To retrieve the boolean value for the epoch id 2, we need to shift the word to the right by 2 bits. We get: 0110 0111 >> 2 = 0001 1001 We then get the value of the last bit by masking with 1. We get: 0001 1001 & 0000 0001 = 0000 0001 = 1 We then return the boolean value true since the last bit is 1.

function _isClaimedEpoch(uint256 _userClaimedEpochs, uint8 _epochId) internal pure returns (bool);

Parameters

NameTypeDescription
_userClaimedEpochsuint256Record of epochs already claimed by the user
_epochIduint8Epoch id to check

Returns

NameTypeDescription
<none>booltrue if the rewards have already been claimed for the given epoch, false otherwise

Events

PromotionCreated

Emitted when a promotion is created.

event PromotionCreated(
uint256 indexed promotionId,
address indexed vault,
IERC20 indexed token,
uint64 startTimestamp,
uint256 tokensPerEpoch,
uint48 epochDuration,
uint8 initialNumberOfEpochs
);

PromotionEnded

Emitted when a promotion is ended.

event PromotionEnded(uint256 indexed promotionId, address indexed recipient, uint256 amount, uint8 epochNumber);

PromotionDestroyed

Emitted when a promotion is destroyed.

event PromotionDestroyed(uint256 indexed promotionId, address indexed recipient, uint256 amount);

PromotionExtended

Emitted when a promotion is extended.

event PromotionExtended(uint256 indexed promotionId, uint256 numberOfEpochs);

RewardsClaimed

Emitted when rewards have been claimed.

event RewardsClaimed(uint256 indexed promotionId, uint8[] epochIds, address indexed user, uint256 amount);

Errors

EpochDurationNotMultipleOfTwabPeriod

Thrown if an epoch duration is not a multiple of the TWAB period length.

error EpochDurationNotMultipleOfTwabPeriod(uint48 epochDuration, uint32 twabPeriodLength);

EpochNotOver

Thrown if the rewards for an epoch are being claimed before the epoch is over.

error EpochNotOver(uint64 epochEndTimestamp);

ExceedsMaxEpochs

Thrown if a promotion extension would exceed the max number of epochs.

error ExceedsMaxEpochs(uint8 epochExtension, uint8 currentEpochs, uint8 maxEpochs);

GracePeriodActive

Thrown if an action cannot be completed while the grace period is active.

error GracePeriodActive(uint256 gracePeriodEndTimestamp);

InvalidEpochId

Thrown if an epoch is outside the range of epochs in a promotion.

error InvalidEpochId(uint8 epochId, uint8 numberOfEpochs);

InvalidPromotion

Thrown if the promotion is invalid or not initialized.

error InvalidPromotion(uint256 promotionId);

OnlyPromotionCreator

Thrown if the sender is not the promotion creator on a creator-only action.

error OnlyPromotionCreator(address sender, address creator);

PayeeZeroAddress

Thrown if the address to receive tokens from ending or destroying a promotion is the zero address.

error PayeeZeroAddress();

PromotionInactive

Thrown if a promotion is no longer active.

error PromotionInactive(uint256 promotionId);

RewardsAlreadyClaimed

Thrown if rewards for the promotion epoch have already been claimed by the user.

error RewardsAlreadyClaimed(uint256 promotionId, address user, uint8 epochId);

StartTimeNotAlignedWithTwabPeriod

Thrown if a promotion start time is not aligned with the start of a TWAB period.

error StartTimeNotAlignedWithTwabPeriod(uint64 startTimePeriodOffset);

TokensReceivedLessThanExpected

Thrown if the tokens received at the creation of a promotion is less than the expected amount.

error TokensReceivedLessThanExpected(uint256 received, uint256 expected);

TwabControllerZeroAddress

Thrown when the TwabController address set in the constructor is the zero address.

error TwabControllerZeroAddress();

ZeroEpochDuration

Thrown when a promotion is created with an epoch duration of zero.

error ZeroEpochDuration();

ZeroEpochs

Thrown when the number of epochs is zero when it must be greater than zero.

error ZeroEpochs();

ZeroTokensPerEpoch

Thrown when a promotion is created with an emission of zero tokens per epoch.

error ZeroTokensPerEpoch();