BYUSDRewardDistributor
Inherits: Owned
Title: BYUSDRewardDistributor
Automatically distributes wrapped rewards to maintain the highest sustainable APR based on current contract balance. Accepts underlying token (e.g. BYUSD) deposits that unlock linearly + one full reward period ahead to pre-fund the next cycle.
Key features:
• Linear vesting with forward unlock (can distribute next full period immediately)
• Distribution amount = max APR the current balance can sustain
• Full anti-dilution + sandwich protection
• Single distribute() call from keeper does everything
State Variables
SECONDS_PER_YEAR
The number of seconds in a year for APR calculations
uint256 private constant SECONDS_PER_YEAR = 36525 * 24 * 60 * 60 / 100
BASIS_POINTS
The number of basis points in 100% (1% = 100 basis points)
uint256 private constant BASIS_POINTS = 10_000
infrared
The address of the Infrared controller contract.
IInfrared public immutable infrared
stakingToken
The address of the staking token associated with the vault.
address public immutable stakingToken
rewardsToken
The ERC20 token used for rewards distribution.
ERC20 public immutable rewardsToken
underlyingToken
The ERC20 token for underlying rewards.
ERC20 public immutable underlyingToken
wrapper
WrappedRewardToken public immutable wrapper
keepers
Mapping of addresses authorized to call the distribute function
Keepers are trusted entities that can trigger reward distributions
mapping(address => bool) public keepers
maxSupplyDeviation
Maximum allowed deviation in total supply to prevent sandwich attacks
Expressed in basis points (e.g., 200 = 2% allowed increase)
uint256 public maxSupplyDeviation = 100
distributionInterval
The interval between distributions in seconds
uint256 public distributionInterval
lastDistributionTime
The timestamp of the last reward distribution.
uint256 public lastDistributionTime
_totalUnderlyingDeposited
uint256 internal _totalUnderlyingDeposited
_totalUnderlyingUnlocked
uint256 internal _totalUnderlyingUnlocked
vests
Vest[] public vests
Functions
constructor
Initializes the RewardDistributor contract.
constructor(
address _gov,
address _infrared,
address _stakingToken,
address _rewardsToken,
address _underlyingToken,
address _keeper,
uint256 _initialDistributionInterval
) Owned(_gov);
Parameters
| Name | Type | Description |
|---|---|---|
_gov | address | The address of the contract owner/governance. |
_infrared | address | The address of the Infrared controller contract. |
_stakingToken | address | The address of the staking token associated with the vault. |
_rewardsToken | address | The address of the ERC20 token used for rewards. |
_underlyingToken | address | The address of the ERC20 token for underlying rewards. |
_keeper | address | Initial keeper address |
_initialDistributionInterval | uint256 | The initial interval between distributions in seconds |
onlyKeeper
Restricts function access to whitelisted keepers only
Reverts if msg.sender is not an active keeper
modifier onlyKeeper() ;
getMaxTotalSupply
Calculates the maximum acceptable total supply for slippage protection
Returns current total supply plus the allowed deviation percentage
function getMaxTotalSupply() external view returns (uint256 totalSupply);
Returns
| Name | Type | Description |
|---|---|---|
totalSupply | uint256 | The maximum acceptable total supply |
getExpectedAmount
Calculates the expected reward amount to be distributed in the next distribution cycle
Accounts for leftover rewards from incomplete periods and residual amounts in vault Returns 0 if distribution conditions are not met (too soon, zero supply, insufficient balance)
function getExpectedAmount() public view returns (uint256 amount);
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | The amount of reward tokens expected to be distributed, or 0 if conditions not met |
getCurrentAPR
Returns the current effective APR based on the vault's active reward rate
Calculates APR from the current rewardRate and totalSupply Returns 0 if no active rewards period or zero total supply
Note: formula: apr = (rewardRate * SECONDS_PER_YEAR * BASIS_POINTS) / totalSupply
function getCurrentAPR() public view returns (uint256 apr);
Returns
| Name | Type | Description |
|---|---|---|
apr | uint256 | The current APR in basis points (e.g., 1500 = 15%) |
getAPRForAmount
Calculates the APR that would result from distributing a specific amount of rewards
Useful for determining how much rewards are needed to achieve a target APR
Note: formula: apr = (amount * SECONDS_PER_YEAR * BASIS_POINTS) / (rewardsDuration * totalSupply)
function getAPRForAmount(uint256 amount) public view returns (uint256 apr);
Parameters
| Name | Type | Description |
|---|---|---|
amount | uint256 | The amount of reward tokens to calculate APR for |
Returns
| Name | Type | Description |
|---|---|---|
apr | uint256 | The resulting APR in basis points if the amount were distributed |
depositUnderlying
Deposit underlying token that will unlock linearly over time
Only keeper (treasury/multisig) can fund rewards to prevent spam and retain control
function depositUnderlying(uint256 amount, uint256 duration)
external
onlyKeeper;
Parameters
| Name | Type | Description |
|---|---|---|
amount | uint256 | Amount of underlying to lock |
duration | uint256 | Total lock duration in seconds (e.g. 30 days) |
unlockableUnderlying
Amount of underlying that can be unlocked right now
Allows unlocking up to one full reward period into the future → ensures next cycle is always pre-funded
function unlockableUnderlying() public view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | unlockable underlying amount |
_unlock
Unlock and wrap all currently available underlying
Also cleans up fully vested entries to prevent storage growth
function _unlock() internal;
distribute
One-call keeper function — does everything
function distribute(uint256 maxTotalSupply) external onlyKeeper;
Parameters
| Name | Type | Description |
|---|---|---|
maxTotalSupply | uint256 | Slippage protection parameter |
updateKeeper
Updates the keeper status for a given address
Only callable by owner, reverts if status would not change
Note: security: Consider checking _keeper != address(0) to prevent accidents
function updateKeeper(address _keeper, bool _active) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
_keeper | address | The address to update keeper status for |
_active | bool | Whether the address should be an active keeper |
setDistributionInterval
Sets the interval between reward distributions
Updates the distribution interval after validating it is non-zero and emits a DistributionIntervalUpdated event
function setDistributionInterval(uint256 _interval) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
_interval | uint256 | The new distribution interval in seconds |
setMaxSupplyDeviation
Sets the maximum allowed supply deviation for slippage protection
Prevents distributions when supply increases beyond this threshold
function setMaxSupplyDeviation(uint256 _deviation) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
_deviation | uint256 | The new maximum deviation in basis points (e.g., 200 = 2%) |
withdrawRewards
Allows the owner to withdraw reward tokens from the contract.
Transfers the specified amount of reward tokens to the owner using safe transfer.
function withdrawRewards(uint256 _amount) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
_amount | uint256 | The amount of reward tokens to withdraw. |
recoverERC20
Recover non-reward tokens sent to this contract by mistake
Cannot be used to withdraw reward tokens - use withdrawRewards instead Transfers entire balance of the specified token to the recipient
Notes:
-
security: Only callable by owner, prevents recovery of reward tokens
-
error: UseWithdrawRewards if attempting to recover reward tokens
-
error: ZeroAmount if token balance is zero
function recoverERC20(address tokenAddress, address to) external onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
tokenAddress | address | Address of the ERC20 token to recover |
to | address | Address to send recovered tokens to |
_getRewardsDuration
function _getRewardsDuration() internal view returns (uint256);
_vault
function _vault() internal view returns (IInfraredVault);
Events
RewardsDistributed
Emitted when rewards are successfully distributed to the vault.
event RewardsDistributed(address vault, uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
vault | address | The address of the Infrared vault receiving the rewards. |
amount | uint256 | The amount of reward tokens distributed. |
DistributionIntervalUpdated
Emitted when the distribution interval is updated
event DistributionIntervalUpdated(uint256 oldInterval, uint256 newInterval);
Parameters
| Name | Type | Description |
|---|---|---|
oldInterval | uint256 | The previous distribution interval in seconds |
newInterval | uint256 | The new distribution interval in seconds |
KeeperUpdated
Emitted when a keeper's status is updated
event KeeperUpdated(address indexed keeper, bool active);
Parameters
| Name | Type | Description |
|---|---|---|
keeper | address | The address of the keeper |
active | bool | Whether the keeper is now active or inactive |
MaxSupplyDeviationUpdated
Emitted when the maximum supply deviation is updated
event MaxSupplyDeviationUpdated(uint256 oldDeviation, uint256 newDeviation);
Parameters
| Name | Type | Description |
|---|---|---|
oldDeviation | uint256 | The previous maximum deviation in basis points |
newDeviation | uint256 | The new maximum deviation in basis points |
TokensRecovered
Emitted when tokens are withdrawn by the owner
event TokensRecovered(address indexed token, address to, uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
token | address | Address of token recovered |
to | address | Address of recipient |
amount | uint256 | Tokens recovered |
UnderlyingDeposited
event UnderlyingDeposited(
address indexed from, uint256 amount, uint256 duration
);
UnlockedAndWrapped
event UnlockedAndWrapped(uint256 underlyingUnlocked);
Errors
ZeroRewardDuration
Thrown when the reward duration from the vault is zero.
error ZeroRewardDuration();
DistributionTooSoon
Thrown when a distribution is attempted before the reward duration has elapsed.
error DistributionTooSoon();
InsufficientRewardBalance
Thrown when the contract has insufficient reward token balance for distribution.
error InsufficientRewardBalance();
ZeroFixedAmount
Thrown when attempting to set a zero fixed reward amount.
error ZeroFixedAmount();
ZeroAddress
Thrown when attempting to set a zero address.
error ZeroAddress();
ZeroDistributionInterval
Thrown when attempting to set a zero distribution interval
error ZeroDistributionInterval();
ZeroTotalSupply
Thrown when the total staked supply in the vault is zero
error ZeroTotalSupply();
NothingToAdd
Thrown when amount to distribute is zero
error NothingToAdd();
NoVault
Thrown when there is no vault for the staking token
error NoVault();
TotalSupplySlippage
Thrown when the current total supply exceeds the maximum allowed (slippage protection)
Prevents distribution during potential sandwich attacks
error TotalSupplySlippage();
NothingToUpdate
Thrown when attempting to update a value that would not change
error NothingToUpdate();
NotKeeper
Thrown when attempting to call onlyKeeper function from non-keeper address
error NotKeeper();
UseWithdrawRewards
Thrown when using recoverERC20 for withdrawing reward tokens
error UseWithdrawRewards();
ZeroAmount
Thrown when calling recoverERC20 with no token balance
error ZeroAmount();
APRWouldDecrease
error APRWouldDecrease();
InvalidAmount
error InvalidAmount();
InvalidDuration
error InvalidDuration();
Structs
Vest
struct Vest {
uint128 amount;
uint64 start;
uint64 duration;
}