RewardDistributor
Inherits: Owned
Distributes reward tokens to the vault to maintain the target APR
This contract integrates with the Infrared controller to distribute rewards to a specified vault. Calculates the reward amount based on the target APR, total staked supply, and reward duration. Uses the vault's rewardRate and periodFinish to compute remaining rewards, accounting for the vault's logic where periodFinish is updated to block.timestamp + rewardsDuration and rewardRate is set to (leftover + newAmount) / rewardsDuration. Protected against sandwich attacks through keeper restrictions and slippage protection on total supply.
Notes:
-
security: The contract grants unlimited approval to the Infrared controller for reward token transfers during initialization. Uses keeper whitelist and slippage protection to prevent manipulation. Only the owner can update configuration parameters or withdraw tokens.
-
access: Only whitelisted keepers can call
distribute
. Only the owner can callsetTargetAPR
,setDistributionInterval
,updateKeeper
,setMaxSupplyDeviation
, andwithdrawRewards
.
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;
targetAPR
The target APR in basis points (e.g., 100 = 1%)
uint256 public targetAPR;
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;
Functions
constructor
Initializes the RewardDistributor contract.
constructor(
address _gov,
address _infrared,
address _stakingToken,
address _rewardsToken,
address _keeper,
uint256 _initialTargetAPR,
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. |
_keeper | address | Initial keeper address |
_initialTargetAPR | uint256 | The initial target APR in basis points (e.g., 100 = 1%) |
_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)
Notes:
-
formula: totalRewardsNeeded = (targetAPR * totalSupply * rewardsDuration) / (SECONDS_PER_YEAR * BASIS_POINTS)
-
formula: amount = totalRewardsNeeded - (leftover + residual)
function getExpectedAmount() external 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() external 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) external 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 |
distribute
Distributes reward tokens to the vault to maintain the target APR
Calculates reward amount based on target APR and current supply, protected against sandwich attacks via keeper restriction and slippage check. Accounts for leftover rewards from previous periods and residual amounts in the vault.
Notes:
-
security: Requires msg.sender to be a whitelisted keeper
-
security: Reverts if current totalSupply exceeds _maxTotalSupply (sandwich attack protection)
-
security: Updates state before external calls to prevent reentrancy
function distribute(uint256 _maxTotalSupply) external onlyKeeper;
Parameters
Name | Type | Description |
---|---|---|
_maxTotalSupply | uint256 | Maximum acceptable total supply (slippage protection) |
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 |
setTargetAPR
Sets the target APR for reward distributions
Updates the target APR after validating it is non-zero and emits a TargetAPRUpdated event
function setTargetAPR(uint256 _apr) external onlyKeeper;
Parameters
Name | Type | Description |
---|---|---|
_apr | uint256 | The new target APR in basis points (e.g., 100 = 1%) |
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 |
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. |
TargetAPRUpdated
Emitted when the target APR is updated
event TargetAPRUpdated(uint256 oldAPR, uint256 newAPR);
Parameters
Name | Type | Description |
---|---|---|
oldAPR | uint256 | The previous target APR in basis points |
newAPR | uint256 | The new target APR in basis points |
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 |
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();
ZeroTargetAPR
Thrown when attempting to set a zero target APR
error ZeroTargetAPR();
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();