# Rewards Claiming Flow
End-to-end execution flow for claiming rewards from Aave V3 RewardsController.
## Quick Reference
| Aspect | Details |
|--------|---------|
| **Entry Points** | `claimRewards`, `claimRewardsOnBehalf`, `claimRewardsToSelf`, `claimAllRewards`, `claimAllRewardsOnBehalf`, `claimAllRewardsToSelf` |
| **Key Transformations** | `Reward Index Updates` |
| **State Changes** | `_assets[asset].rewards[reward].usersData[user].accrued -= amount` |
| **Events Emitted** | `RewardsClaimed` |
---
## Flow Diagram
```mermaid
flowchart TD
%% Styling definitions
classDef validation fill:#ffcccc,stroke:#ff0000,stroke-width:2px
classDef transformation fill:#ccffcc,stroke:#00aa00,stroke-width:2px
classDef storage fill:#ccccff,stroke:#0000ff,stroke-width:2px
classDef event fill:#ffffcc,stroke:#aaaa00,stroke-width:2px
classDef error fill:#ff0000,stroke:#000000,color:#fff
classDef critical stroke:#ff0000,stroke-width:3px
%% Entry points
Entry1["claimRewards / claimRewardsToSelf"] --> ValidateTo1["VALIDATION
to != address"]
Entry2["claimRewardsOnBehalf"] --> ValidateUser["VALIDATION
claimer authorized
user != address"]
Entry3["claimAllRewards / claimAllRewardsToSelf"] --> ValidateTo2["VALIDATION
to != address"]
Entry4["claimAllRewardsOnBehalf"] --> ValidateUser2["VALIDATION
claimer authorized
user != address"]
class ValidateTo1 validation
class ValidateUser validation
class ValidateTo2 validation
class ValidateUser2 validation
ValidateTo1 --> GetBalances["_getUserAssetBalances
Get scaled balances
for all assets"]
ValidateUser --> GetBalances
ValidateTo2 --> GetBalances
ValidateUser2 --> GetBalances
subgraph DataUpdate ["1. Data Update Phase"]
direction TB
UpdateData["_updateDataMultiple
Iterate all assets"] --> UpdateAsset["For each asset:
_updateData"]
UpdateAsset --> UpdateReward["TRANSFORMATION
_updateRewardData
Calculate new index
based on time delta"]
class UpdateReward transformation
UpdateReward --> UpdateUser["TRANSFORMATION
_updateUserData
Calculate accrued rewards"]
class UpdateUser transformation
UpdateUser --> StoreAccrued["STORAGE UPDATE
usersData[user].accrued +=
rewardsAccrued"]
class StoreAccrued storage
StoreAccrued --> EmitAccrued["EMIT
Accrued"]
class EmitAccrued event
end
GetBalances --> DataUpdate
subgraph ClaimCalc ["2. Claim Calculation Phase"]
direction TB
SingleReward{"Single or
All Rewards?"}
SingleReward -->|Single| SumAccrued["Sum accrued rewards
across all assets
for specific reward"]
SingleReward -->|All| IterateRewards["Iterate all rewards
across all assets"]
SumAccrued --> AmountCheck{"amount == 0?"}
class AmountCheck validation
AmountCheck -->|Yes| ReturnZero["Return 0"]
AmountCheck -->|No| CheckLimit{"totalRewards >
requested amount?"}
CheckLimit -->|total <= amount| ClearAll["STORAGE UPDATE
accrued = 0
for all assets"]
class ClearAll storage
CheckLimit -->|total > amount| PartialClaim["Calculate difference
STORAGE UPDATE
accrued = difference"]
class PartialClaim storage
IterateRewards --> AccumulateAll["Accumulate all
reward amounts
per reward token"]
AccumulateAll --> ClearAllMulti["STORAGE UPDATE
accrued = 0
for claimed amounts"]
class ClearAllMulti storage
end
DataUpdate --> SingleReward
subgraph TransferPhase ["3. Reward Transfer Phase"]
direction TB
CheckZero{"totalRewards
== 0?"}
class CheckZero validation
CheckZero -->|Yes| ReturnZero2["Return 0"]
CheckZero -->|No| GetStrategy["Get TransferStrategy
for reward token"]
GetStrategy --> PerformTransfer["_transferRewards
delegatecall to
TransferStrategy"]
PerformTransfer --> TransferSuccess{"Transfer
successful?"}
class TransferSuccess validation
TransferSuccess -->|No| RevertError["REVERT
TRANSFER_ERROR"]
class RevertError error
TransferSuccess -->|Yes| EmitClaimed["EMIT
RewardsClaimed"]
class EmitClaimed event
end
ClearAll --> CheckZero
PartialClaim --> CheckZero
ClearAllMulti --> CheckZero
%% Single reward transfer vs All rewards transfer
SingleReward -.->|All| MultiTransfer["Loop through
all reward tokens
_transferRewards each"]
MultiTransfer --> EmitClaimed
EmitClaimed --> ReturnAmount["Return
claimed amount(s)"]
```
---
## Step-by-Step Execution
### 1. Entry Points
**File:** `contracts/rewards/RewardsController.sol`
```solidity
function claimRewards(
address[] calldata assets,
uint256 amount,
address to,
address reward
) external override returns (uint256) {
require(to != address(0), 'INVALID_TO_ADDRESS');
return _claimRewards(assets, amount, msg.sender, msg.sender, to, reward);
}
function claimRewardsOnBehalf(
address[] calldata assets,
uint256 amount,
address user,
address to,
address reward
) external override onlyAuthorizedClaimers(msg.sender, user) returns (uint256) {
require(user != address(0), 'INVALID_USER_ADDRESS');
require(to != address(0), 'INVALID_TO_ADDRESS');
return _claimRewards(assets, amount, msg.sender, user, to, reward);
}
function claimRewardsToSelf(
address[] calldata assets,
uint256 amount,
address reward
) external override returns (uint256) {
return _claimRewards(assets, amount, msg.sender, msg.sender, msg.sender, reward);
}
function claimAllRewards(address[] calldata assets, address to)
external
override
returns (address[] memory rewardsList, uint256[] memory claimedAmounts)
{
require(to != address(0), 'INVALID_TO_ADDRESS');
return _claimAllRewards(assets, msg.sender, msg.sender, to);
}
function claimAllRewardsOnBehalf(
address[] calldata assets,
address user,
address to
)
external
override
onlyAuthorizedClaimers(msg.sender, user)
returns (address[] memory rewardsList, uint256[] memory claimedAmounts)
{
require(user != address(0), 'INVALID_USER_ADDRESS');
require(to != address(0), 'INVALID_TO_ADDRESS');
return _claimAllRewards(assets, msg.sender, user, to);
}
function claimAllRewardsToSelf(address[] calldata assets)
external
override
returns (address[] memory rewardsList, uint256[] memory claimedAmounts)
{
return _claimAllRewards(assets, msg.sender, msg.sender, msg.sender);
}
```
### 2. Get User Asset Balances
**File:** `contracts/rewards/RewardsController.sol`
```solidity
function _getUserAssetBalances(address[] calldata assets, address user)
internal
view
override
returns (RewardsDataTypes.UserAssetBalance[] memory userAssetBalances)
{
userAssetBalances = new RewardsDataTypes.UserAssetBalance[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
userAssetBalances[i].asset = assets[i];
(userAssetBalances[i].userBalance, userAssetBalances[i].totalSupply) = IScaledBalanceToken(
assets[i]
).getScaledUserBalanceAndSupply(user);
}
return userAssetBalances;
}
```
### 3. Update Data Multiple
**File:** `contracts/rewards/RewardsDistributor.sol`
```solidity
function _updateDataMultiple(
address user,
RewardsDataTypes.UserAssetBalance[] memory userAssetBalances
) internal {
for (uint256 i = 0; i < userAssetBalances.length; i++) {
_updateData(
userAssetBalances[i].asset,
user,
userAssetBalances[i].userBalance,
userAssetBalances[i].totalSupply
);
}
}
```
### 4. Update Data (Per Asset)
**File:** `contracts/rewards/RewardsDistributor.sol`
```solidity
function _updateData(
address asset,
address user,
uint256 userBalance,
uint256 totalSupply
) internal {
uint256 assetUnit;
uint256 numAvailableRewards = _assets[asset].availableRewardsCount;
unchecked {
assetUnit = 10**_assets[asset].decimals;
}
if (numAvailableRewards == 0) {
return;
}
unchecked {
for (uint128 r = 0; r < numAvailableRewards; r++) {
address reward = _assets[asset].availableRewards[r];
RewardsDataTypes.RewardData storage rewardData = _assets[asset].rewards[reward];
(uint256 newAssetIndex, bool rewardDataUpdated) = _updateRewardData(
rewardData,
totalSupply,
assetUnit
);
(uint256 rewardsAccrued, bool userDataUpdated) = _updateUserData(
rewardData,
user,
userBalance,
newAssetIndex,
assetUnit
);
if (rewardDataUpdated || userDataUpdated) {
emit Accrued(asset, reward, user, newAssetIndex, newAssetIndex, rewardsAccrued);
}
}
}
}
```
### 5. Update Reward Data (Index Calculation)
**File:** `contracts/rewards/RewardsDistributor.sol`
```solidity
function _updateRewardData(
RewardsDataTypes.RewardData storage rewardData,
uint256 totalSupply,
uint256 assetUnit
) internal returns (uint256, bool) {
(uint256 oldIndex, uint256 newIndex) = _getAssetIndex(rewardData, totalSupply, assetUnit);
bool indexUpdated;
if (newIndex != oldIndex) {
require(newIndex <= type(uint104).max, 'INDEX_OVERFLOW');
indexUpdated = true;
//optimization: storing one after another saves one SSTORE
rewardData.index = uint104(newIndex);
rewardData.lastUpdateTimestamp = block.timestamp.toUint32();
} else {
rewardData.lastUpdateTimestamp = block.timestamp.toUint32();
}
return (newIndex, indexUpdated);
}
```
### 6. Get Asset Index
**File:** `contracts/rewards/RewardsDistributor.sol`
```solidity
function _getAssetIndex(
RewardsDataTypes.RewardData storage rewardData,
uint256 totalSupply,
uint256 assetUnit
) internal view returns (uint256, uint256) {
uint256 oldIndex = rewardData.index;
uint256 distributionEnd = rewardData.distributionEnd;
uint256 emissionPerSecond = rewardData.emissionPerSecond;
uint256 lastUpdateTimestamp = rewardData.lastUpdateTimestamp;
if (
emissionPerSecond == 0 ||
totalSupply == 0 ||
lastUpdateTimestamp == block.timestamp ||
lastUpdateTimestamp >= distributionEnd
) {
return (oldIndex, oldIndex);
}
uint256 currentTimestamp = block.timestamp > distributionEnd
? distributionEnd
: block.timestamp;
uint256 timeDelta = currentTimestamp - lastUpdateTimestamp;
uint256 firstTerm = emissionPerSecond * timeDelta * assetUnit;
assembly {
firstTerm := div(firstTerm, totalSupply)
}
return (oldIndex, (firstTerm + oldIndex));
}
```
### 7. Update User Data
**File:** `contracts/rewards/RewardsDistributor.sol`
```solidity
function _updateUserData(
RewardsDataTypes.RewardData storage rewardData,
address user,
uint256 userBalance,
uint256 newAssetIndex,
uint256 assetUnit
) internal returns (uint256, bool) {
uint256 userIndex = rewardData.usersData[user].index;
uint256 rewardsAccrued;
bool dataUpdated;
if ((dataUpdated = userIndex != newAssetIndex)) {
// already checked for overflow in _updateRewardData
rewardData.usersData[user].index = uint104(newAssetIndex);
if (userBalance != 0) {
rewardsAccrued = _getRewards(userBalance, newAssetIndex, userIndex, assetUnit);
rewardData.usersData[user].accrued += rewardsAccrued.toUint128();
}
}
return (rewardsAccrued, dataUpdated);
}
```
### 8. Get Rewards Calculation
**File:** `contracts/rewards/RewardsDistributor.sol`
```solidity
function _getRewards(
uint256 userBalance,
uint256 reserveIndex,
uint256 userIndex,
uint256 assetUnit
) internal pure returns (uint256) {
uint256 result = userBalance * (reserveIndex - userIndex);
assembly {
result := div(result, assetUnit)
}
return result;
}
```
### 9. Claim Rewards (Single Reward)
**File:** `contracts/rewards/RewardsController.sol`
```solidity
function _claimRewards(
address[] calldata assets,
uint256 amount,
address claimer,
address user,
address to,
address reward
) internal returns (uint256) {
if (amount == 0) {
return 0;
}
uint256 totalRewards;
_updateDataMultiple(user, _getUserAssetBalances(assets, user));
for (uint256 i = 0; i < assets.length; i++) {
address asset = assets[i];
totalRewards += _assets[asset].rewards[reward].usersData[user].accrued;
if (totalRewards <= amount) {
_assets[asset].rewards[reward].usersData[user].accrued = 0;
} else {
uint256 difference = totalRewards - amount;
totalRewards -= difference;
_assets[asset].rewards[reward].usersData[user].accrued = difference.toUint128();
break;
}
}
if (totalRewards == 0) {
return 0;
}
_transferRewards(to, reward, totalRewards);
emit RewardsClaimed(user, reward, to, claimer, totalRewards);
return totalRewards;
}
```
### 10. Claim All Rewards
**File:** `contracts/rewards/RewardsController.sol`
```solidity
function _claimAllRewards(
address[] calldata assets,
address claimer,
address user,
address to
) internal returns (address[] memory rewardsList, uint256[] memory claimedAmounts) {
uint256 rewardsListLength = _rewardsList.length;
rewardsList = new address[](rewardsListLength);
claimedAmounts = new uint256[](rewardsListLength);
_updateDataMultiple(user, _getUserAssetBalances(assets, user));
for (uint256 i = 0; i < assets.length; i++) {
address asset = assets[i];
for (uint256 j = 0; j < rewardsListLength; j++) {
if (rewardsList[j] == address(0)) {
rewardsList[j] = _rewardsList[j];
}
uint256 rewardAmount = _assets[asset].rewards[rewardsList[j]].usersData[user].accrued;
if (rewardAmount != 0) {
claimedAmounts[j] += rewardAmount;
_assets[asset].rewards[rewardsList[j]].usersData[user].accrued = 0;
}
}
}
for (uint256 i = 0; i < rewardsListLength; i++) {
_transferRewards(to, rewardsList[i], claimedAmounts[i]);
emit RewardsClaimed(user, rewardsList[i], to, claimer, claimedAmounts[i]);
}
return (rewardsList, claimedAmounts);
}
```
### 11. Transfer Rewards
**File:** `contracts/rewards/RewardsController.sol`
```solidity
function _transferRewards(
address to,
address reward,
uint256 amount
) internal {
ITransferStrategyBase transferStrategy = _transferStrategy[reward];
bool success = transferStrategy.performTransfer(to, reward, amount);
require(success == true, 'TRANSFER_ERROR');
}
```
---
## Amount Transformations
### Reward Index Calculation
```
oldIndex = rewardData.index
emissionPerSecond = rewardData.emissionPerSecond
lastUpdateTimestamp = rewardData.lastUpdateTimestamp
distributionEnd = rewardData.distributionEnd
totalSupply = IScaledBalanceToken(asset).scaledTotalSupply()
assetUnit = 10^decimals
↓
if (emissionPerSecond == 0 || totalSupply == 0 ||
lastUpdateTimestamp == block.timestamp ||
lastUpdateTimestamp >= distributionEnd):
newIndex = oldIndex
↓
currentTimestamp = min(block.timestamp, distributionEnd)
timeDelta = currentTimestamp - lastUpdateTimestamp
firstTerm = (emissionPerSecond * timeDelta * assetUnit) / totalSupply
newIndex = firstTerm + oldIndex
↓
rewardData.index = newIndex (uint104)
rewardData.lastUpdateTimestamp = block.timestamp (uint32)
```
### User Rewards Accrual
```
userIndex = rewardData.usersData[user].index (stored user index)
newAssetIndex = calculated above
userBalance = user's scaled balance
↓
if (userIndex != newAssetIndex):
rewardsAccrued = (userBalance * (newAssetIndex - userIndex)) / assetUnit
rewardData.usersData[user].index = newAssetIndex (uint104)
rewardData.usersData[user].accrued += rewardsAccrued (uint128)
```
### Claim Amount Calculation
```
Single Reward Claim:
totalRewards = sum of accrued across all assets for specific reward
if (totalRewards <= amount):
claimed = totalRewards
accrued = 0 for all assets
else:
claimed = amount
difference = totalRewards - amount
accrued = difference (partial claim)
All Rewards Claim:
For each reward in _rewardsList:
claimedAmounts[j] = sum of accrued across all assets
accrued = 0 for claimed amounts
```
**Key Points:**
- Reward index is stored as `uint104` (max ~2.02e31)
- Accrued rewards stored as `uint128` (max ~3.4e38)
- Timestamps stored as `uint32` (valid until year 2106)
- Emission per second stored as `uint88` (max ~3.1e26)
- Index calculation uses assembly for division to save gas
---
## Event Details
### RewardsClaimed Event
```solidity
event RewardsClaimed(
address indexed user, // User whose rewards were claimed
address indexed reward, // Reward token address
address indexed to, // Recipient of the rewards
address claimer, // Address that initiated the claim
uint256 amount // Amount of rewards claimed
);
```
### Accrued Event
Emitted during the data update phase when rewards are accrued for a user.
```solidity
event Accrued(
address indexed asset, // Asset being incentivized
address indexed reward, // Reward token address
address indexed user, // User address
uint256 assetIndex, // New asset index
uint256 userIndex, // New user index
uint256 rewardsAccrued // Amount of rewards accrued
);
```
### ClaimerSet Event
Emitted when an authorized claimer is set for a user.
```solidity
event ClaimerSet(
address indexed user, // User address
address indexed claimer // Authorized claimer address
);
```
### TransferStrategyInstalled Event
Emitted when a transfer strategy is configured for a reward token.
```solidity
event TransferStrategyInstalled(
address indexed reward, // Reward token address
address indexed transferStrategy // Transfer strategy contract
);
```
### RewardOracleUpdated Event
Emitted when a price oracle is set for a reward token.
```solidity
event RewardOracleUpdated(
address indexed reward, // Reward token address
address indexed rewardOracle // Oracle contract address
);
```
---
## Error Conditions
| Error | Condition | File |
|-------|-----------|------|
| `INVALID_TO_ADDRESS` | `to == address(0)` | RewardsController.sol |
| `INVALID_USER_ADDRESS` | `user == address(0)` | RewardsController.sol |
| `CLAIMER_UNAUTHORIZED` | `msg.sender != _authorizedClaimers[user]` | RewardsController.sol |
| `TRANSFER_ERROR` | Transfer strategy returns `false` | RewardsController.sol |
| `INDEX_OVERFLOW` | `newIndex > type(uint104).max` | RewardsDistributor.sol |
| `ONLY_EMISSION_MANAGER` | `msg.sender != EMISSION_MANAGER` | RewardsDistributor.sol |
| `INVALID_INPUT` | Array length mismatch in `setEmissionPerSecond` | RewardsDistributor.sol |
| `DISTRIBUTION_DOES_NOT_EXIST` | Asset not configured | RewardsDistributor.sol |
| `STRATEGY_CAN_NOT_BE_ZERO` | Transfer strategy is zero address | RewardsController.sol |
| `STRATEGY_MUST_BE_CONTRACT` | Transfer strategy is not a contract | RewardsController.sol |
| `ORACLE_MUST_RETURN_PRICE` | Oracle returns zero or negative price | RewardsController.sol |
---
## Related Flows
- [Supply Flow](./supply.md) - Users accrue rewards by supplying assets
- [Borrow Flow](./borrow.md) - Users may accrue rewards by borrowing (if configured)
- [Withdraw Flow](./withdraw.md) - Rewards update when user balance changes
- [Repay Flow](./repay.md) - Rewards update when debt balance changes
---
## Source File Locations
```
contracts/rewards/RewardsController.sol
contracts/rewards/RewardsDistributor.sol
contracts/rewards/interfaces/IRewardsController.sol
contracts/rewards/interfaces/IRewardsDistributor.sol
contracts/rewards/interfaces/ITransferStrategyBase.sol
contracts/rewards/libraries/RewardsDataTypes.sol
contracts/misc/interfaces/IEACAggregatorProxy.sol
```
---
## Data Structures
### RewardData
```solidity
struct RewardData {
uint104 index; // Liquidity index of the reward distribution
uint88 emissionPerSecond; // Amount of reward tokens distributed per second
uint32 lastUpdateTimestamp; // Timestamp of the last reward index update
uint32 distributionEnd; // The end of the distribution of rewards (in seconds)
mapping(address => UserData) usersData;
}
```
### UserData
```solidity
struct UserData {
uint104 index; // Liquidity index of the reward distribution for the user
uint128 accrued; // Amount of accrued rewards for the user since last update
}
```
### UserAssetBalance
```solidity
struct UserAssetBalance {
address asset; // Asset address
uint256 userBalance; // User's scaled balance
uint256 totalSupply; // Total scaled supply
}
```