stkAAVE Staking Flow¶
End-to-end execution flow for staking AAVE tokens to receive stkAAVE shares.
Quick Reference¶
Aspect |
Details |
|---|---|
Entry Points |
|
Key Transformations |
|
State Changes |
|
Events Emitted |
|
Flow Diagram¶
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
%% Entry points
Entry1["stake(to, amount)"] --> CommonStake
Entry2["stakeWithPermit(amount, deadline, v, r, s)"] --> Permit["STAKED_TOKEN.permit<br/>Gasless approval"] --> CommonStake
Entry3["claimRewardsAndStake(to, amount)"] --> ClaimStake["_claimRewardsAndStakeOnBehalf"]
Entry4["claimRewardsAndStakeOnBehalf(from, to, amount)"] --> ClaimStake
%% Main staking flow
CommonStake["_stake(from, to, amount)"] --> Validate1["VALIDATION<br/>!inPostSlashingPeriod<br/>amount != 0"]
class Validate1 validation
ClaimStake --> ValidateClaim["VALIDATION<br/>REWARD_TOKEN == STAKED_TOKEN"]
class ValidateClaim validation
ValidateClaim --> UpdateRewardsClaim["Update unclaimed rewards<br/>_updateCurrentUnclaimedRewards"]
UpdateRewardsClaim --> ClaimRewards["Claim rewards to contract<br/>_claimRewards"]
ClaimRewards --> CommonStake
%% Staking process
Validate1 --> GetBalance["Get recipient balance<br/>balanceOf(to)"]
subgraph RewardsUpdate ["1. Rewards Accrual"]
direction TB
UpdateUserAsset["_updateUserAssetInternal<br/>Calculate accrued rewards"] --> CheckRewards{"Accrued > 0?"}
CheckRewards -->|Yes| StoreRewards["STORAGE UPDATE<br/>stakerRewardsToClaim[to]<br/>+= accrued"]
class StoreRewards storage
CheckRewards -->|No| SkipRewards[Continue]
StoreRewards --> RewardsEvent["EMIT<br/>RewardsAccrued"]
class RewardsEvent event
end
GetBalance --> RewardsUpdate
SkipRewards --> CalcShares
RewardsEvent --> CalcShares
subgraph TokenFlow ["2. Token Flow & Share Minting"]
direction TB
CalcShares["TRANSFORMATION<br/>sharesToMint =<br/>previewStake(amount)"]
class CalcShares transformation
CalcShares --> Transfer["IERC20<br/>safeTransferFrom<br/>from -> contract"]
Transfer --> Mint["_mint(to,<br/>sharesToMint.toUint104)"]
Mint --> UpdateStorage["STORAGE UPDATE<br/>_balances[to] +=<br/>sharesToMint<br/>_totalSupply +=<br/>sharesToMint"]
class UpdateStorage storage
end
%% Final event
UpdateStorage --> FinalEvent["EMIT<br/>Staked"]
class FinalEvent event
%% Error annotations
%% CRITICAL: All validations must pass or transaction reverts
%% CRITICAL: Slashing period blocks new stakes
%% CRITICAL: Permit failures are silently ignored (try/catch)
Step-by-Step Execution¶
1. Entry Points¶
1a. Direct Stake¶
File: StakedTokenV3.sol
function stake(address to, uint256 amount) external {
_stake(msg.sender, to, amount);
}
1b. Stake with Permit (Gasless Approval)¶
File: StakedTokenV3.sol
function stakeWithPermit(
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
try
IERC20WithPermit(address(STAKED_TOKEN)).permit(
msg.sender,
address(this),
amount,
deadline,
v,
r,
s
)
{
// do nothing
} catch (bytes memory) {
// do nothing - permit failure is silently ignored
}
_stake(msg.sender, msg.sender, amount);
}
Note: The permit call is wrapped in a try/catch block. If the permit fails (e.g., already used), the stake proceeds assuming existing approval.
1c. Claim Rewards and Stake¶
File: StakedAaveV3.sol
function claimRewardsAndStake(address to, uint256 amount) external returns (uint256) {
return _claimRewardsAndStakeOnBehalf(msg.sender, to, amount);
}
function claimRewardsAndStakeOnBehalf(
address from,
address to,
uint256 amount
) external onlyClaimHelper returns (uint256) {
return _claimRewardsAndStakeOnBehalf(from, to, amount);
}
2. Internal Staking Implementation¶
File: StakedTokenV3.sol
function _stake(address from, address to, uint256 amount) internal {
// [VALIDATION] Prevent staking during slashing period
require(!inPostSlashingPeriod, 'SLASHING_ONGOING');
require(amount != 0, 'INVALID_ZERO_AMOUNT');
uint256 balanceOfTo = balanceOf(to);
// Update rewards for the recipient before stake
uint256 accruedRewards = _updateUserAssetInternal(
to,
address(this),
balanceOfTo,
totalSupply()
);
// Accumulate any accrued rewards to claimable balance
if (accruedRewards != 0) {
stakerRewardsToClaim[to] = stakerRewardsToClaim[to] + accruedRewards;
emit RewardsAccrued(to, accruedRewards);
}
// [TRANSFORMATION] Calculate shares to mint based on exchange rate
uint256 sharesToMint = previewStake(amount);
// Transfer staked tokens from user to contract
STAKED_TOKEN.safeTransferFrom(from, address(this), amount);
// Mint shares to recipient
_mint(to, sharesToMint.toUint104());
// [EVENT] Emit staking event
emit Staked(from, to, amount, sharesToMint);
}
3. Claim and Stake Implementation¶
File: StakedAaveV3.sol
function _claimRewardsAndStakeOnBehalf(
address from,
address to,
uint256 amount
) internal returns (uint256) {
// [VALIDATION] Only works when reward token is same as staked token (AAVE)
require(REWARD_TOKEN == STAKED_TOKEN, 'REWARD_TOKEN_IS_NOT_STAKED_TOKEN');
// Update rewards calculation for the user
uint256 userUpdatedRewards = _updateCurrentUnclaimedRewards(
from,
balanceOf(from),
true
);
// Determine amount to claim (capped at available rewards)
uint256 amountToClaim = (amount > userUpdatedRewards)
? userUpdatedRewards
: amount;
if (amountToClaim != 0) {
// Claim rewards to this contract first
_claimRewards(from, address(this), amountToClaim);
// Stake the claimed rewards
_stake(address(this), to, amountToClaim);
}
return amountToClaim;
}
4. Preview and Exchange Rate Functions¶
File: StakedTokenV3.sol
function previewStake(uint256 assets) public view returns (uint256) {
return (assets * _currentExchangeRate) / EXCHANGE_RATE_UNIT;
}
function getExchangeRate() public view returns (uint216) {
return _currentExchangeRate;
}
function previewRedeem(uint256 shares) public view returns (uint256) {
return (EXCHANGE_RATE_UNIT * shares) / _currentExchangeRate;
}
Amount Transformations¶
Exchange Rate Mechanics¶
The exchange rate represents the ratio of shares to underlying assets:
Initial Rate: 1:1 (1 share = 1 asset)
After Rewards: Rate increases as rewards are distributed to stakers
After Slashing: Rate decreases to reflect loss of underlying assets
Exchange Rate Formula:
function _getExchangeRate(uint256 totalAssets, uint256 totalShares)
internal pure returns (uint216)
{
return (((totalShares * EXCHANGE_RATE_UNIT) + totalAssets - 1) / totalAssets);
}
Key Points:
EXCHANGE_RATE_UNIT= 10^18 (18 decimal precision)Rounds up to ensure 100% backing of shares (favors the contract)
Exchange rate increases when rewards are added to the pool
Exchange rate decreases during slashing events
Event Details¶
Staked Event¶
Emitted whenever AAVE tokens are successfully staked.
event Staked(
address indexed from, // Address providing the AAVE tokens
address indexed to, // Address receiving the stkAAVE shares
uint256 assets, // Amount of AAVE tokens staked
uint256 shares // Amount of stkAAVE shares minted
);
RewardsAccrued Event¶
Emitted when pending rewards are calculated and stored during the staking process.
event RewardsAccrued(
address indexed user, // User whose rewards accrued
uint256 amount // Amount of rewards accrued
);
ExchangeRateChanged Event¶
Emitted when the exchange rate is updated (during slashing or return of funds).
event ExchangeRateChanged(
uint216 exchangeRate // New exchange rate (18 decimal precision)
);
Error Conditions¶
Error |
Condition |
File |
|---|---|---|
|
|
StakedTokenV3.sol |
|
|
StakedTokenV3.sol |
|
|
StakedAaveV3.sol |
|
User has no balance when calling cooldown |
StakedTokenV3.sol |
|
Cooldown period not yet complete |
StakedTokenV3.sol |
|
Redemption window has passed |
StakedTokenV3.sol |
|
No redeemable balance available |
StakedTokenV3.sol |
Source File Locations¶
contract_reference/aave/stkAAVE/rev_6.sol (StakedAaveV3 - main implementation)
├── stake(to, amount)
├── stakeWithPermit(amount, deadline, v, r, s)
├── claimRewardsAndStake(to, amount)
├── claimRewardsAndStakeOnBehalf(from, to, amount)
└── _claimRewardsAndStakeOnBehalf(from, to, amount)
StakedTokenV3 (inherited)
├── _stake(from, to, amount)
├── previewStake(assets)
├── previewRedeem(shares)
├── getExchangeRate()
└── _getExchangeRate(totalAssets, totalShares)