# stkAAVE Unstaking Flow
End-to-end execution flow for unstaking stkAAVE shares to receive AAVE tokens via the two-step cooldown and redeem process.
## Quick Reference
| Aspect | Details |
|--------|---------|
| **Entry Points** | `cooldown()`, `cooldownOnBehalfOf(from)`, `redeem(to, amount)`, `redeemOnBehalf(from, to, amount)` |
| **Key Transformations** | [Shares → Assets via Exchange Rate](#amount-transformations) |
| **State Changes** | `stakersCooldowns[from] = CooldownSnapshot`, `_balances[from] -= shares`, `_totalSupply -= shares` |
| **Events Emitted** | `Cooldown`, `Redeem` |
---
## 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
%% Entry points - Cooldown
Entry1["cooldown()"] --> CooldownInternal["_cooldown(from)"]
Entry2["cooldownOnBehalfOf(from)"] --> CooldownInternal
%% Cooldown flow
CooldownInternal --> ValidateBalance["VALIDATION
balanceOf(from) != 0"]
class ValidateBalance validation
ValidateBalance --> StoreCooldown["STORAGE UPDATE
stakersCooldowns[from]
= CooldownSnapshot
timestamp: block.timestamp
amount: balanceOf(from)"]
class StoreCooldown storage
StoreCooldown --> EventCooldown["EMIT
Cooldown"]
class EventCooldown event
%% Entry points - Redeem
Entry3["redeem(to, amount)"] --> RedeemInternal["_redeem(from, to, amount)"]
Entry4["redeemOnBehalf(from, to, amount)"] --> RedeemInternal
%% Redeem flow
RedeemInternal --> ValidateAmount["VALIDATION
amount != 0"]
class ValidateAmount validation
ValidateAmount --> CheckCooldown["VALIDATION
Cooldown Period Checks"]
class CheckCooldown validation
CheckCooldown --> GetCooldown["Get CooldownSnapshot
stakersCooldowns[from]"]
GetCooldown --> CheckNotSlashing{"inPostSlashingPeriod?"}
CheckNotSlashing -->|No| ValidateCooldownTime["VALIDATION
block.timestamp >=
cooldownSnapshot.timestamp +
_cooldownSeconds"]
class ValidateCooldownTime validation
ValidateCooldownTime --> ValidateWindow["VALIDATION
block.timestamp -
(cooldownSnapshot.timestamp +
_cooldownSeconds) <=
UNSTAKE_WINDOW"]
class ValidateWindow validation
CheckNotSlashing -->|Yes| SkipCooldown[Skip cooldown checks]
ValidateWindow --> GetMaxRedeemable
SkipCooldown --> GetMaxRedeemable["Get maxRedeemable
inPostSlashingPeriod ?
balanceOf(from) :
cooldownSnapshot.amount"]
GetMaxRedeemable --> ValidateMaxRedeemable["VALIDATION
maxRedeemable != 0"]
class ValidateMaxRedeemable validation
ValidateMaxRedeemable --> CalcRedeem["TRANSFORMATION
amountToRedeem =
min(amount, maxRedeemable)"]
class CalcRedeem transformation
CalcRedeem --> UpdateRewards["Update unclaimed rewards
_updateCurrentUnclaimedRewards"]
subgraph TokenFlow ["Token Flow & Share Burning"]
direction TB
CalcAssets["TRANSFORMATION
underlyingToRedeem =
previewRedeem(amountToRedeem)"]
class CalcAssets transformation
CalcAssets --> Burn["_burn(from,
amountToRedeem)"]
Burn --> UpdateSupply["STORAGE UPDATE
_balances[from] -=
amountToRedeem
_totalSupply -=
amountToRedeem"]
class UpdateSupply storage
end
UpdateRewards --> TokenFlow
%% Cooldown state update
UpdateSupply --> CheckCooldownUpdate{"cooldownSnapshot
.timestamp != 0?"}
CheckCooldownUpdate -->|Yes| CheckFullRedeem{"cooldownSnapshot.amount -
amountToRedeem == 0?"}
CheckFullRedeem -->|Yes| DeleteCooldown["STORAGE UPDATE
delete stakersCooldowns[from]"]
class DeleteCooldown storage
CheckFullRedeem -->|No| UpdateCooldownAmount["STORAGE UPDATE
stakersCooldowns[from].amount
-= amountToRedeem"]
class UpdateCooldownAmount storage
CheckCooldownUpdate -->|No| SkipCooldownUpdate[Continue]
DeleteCooldown --> Transfer
UpdateCooldownAmount --> Transfer
SkipCooldownUpdate --> Transfer
%% Final transfer and event
Transfer["IERC20
safeTransfer
to -> underlyingToRedeem"] --> FinalEvent["EMIT
Redeem"]
class FinalEvent event
%% Error annotations
%% CRITICAL: Cooldown must be completed before redeeming
%% CRITICAL: Redemption must happen within UNSTAKE_WINDOW after cooldown
%% CRITICAL: During post-slashing period, cooldown checks are bypassed
```
---
## Step-by-Step Execution
### 1. Cooldown Entry Points
#### 1a. Direct Cooldown
**File:** `StakedTokenV3.sol`
```solidity
function cooldown() external override(IStakedTokenV2, StakedTokenV2) {
_cooldown(msg.sender);
}
```
#### 1b. Cooldown On Behalf
**File:** `StakedTokenV3.sol`
```solidity
function cooldownOnBehalfOf(address from) external override onlyClaimHelper {
_cooldown(from);
}
```
**Note:** Only the claim helper contract can call `cooldownOnBehalfOf`.
### 2. Internal Cooldown Implementation
**File:** `StakedTokenV3.sol`
```solidity
function _cooldown(address from) internal {
// [VALIDATION] User must have a balance to cooldown
uint256 amount = balanceOf(from);
require(amount != 0, 'INVALID_BALANCE_ON_COOLDOWN');
// [STORAGE UPDATE] Store cooldown snapshot with current timestamp and full balance
stakersCooldowns[from] = CooldownSnapshot({
timestamp: uint40(block.timestamp),
amount: uint216(amount)
});
// [EVENT] Emit cooldown started event
emit Cooldown(from, amount);
}
```
### 3. Redeem Entry Points
#### 3a. Direct Redeem
**File:** `StakedTokenV3.sol`
```solidity
function redeem(
address to,
uint256 amount
) external override(IStakedTokenV2, StakedTokenV2) {
_redeem(msg.sender, to, amount.toUint104());
}
```
#### 3b. Redeem On Behalf
**File:** `StakedTokenV3.sol`
```solidity
function redeemOnBehalf(
address from,
address to,
uint256 amount
) external override onlyClaimHelper {
_redeem(from, to, amount.toUint104());
}
```
### 4. Internal Redeem Implementation
**File:** `StakedTokenV3.sol`
```solidity
function _redeem(address from, address to, uint104 amount) internal {
// [VALIDATION] Amount must be non-zero
require(amount != 0, 'INVALID_ZERO_AMOUNT');
CooldownSnapshot memory cooldownSnapshot = stakersCooldowns[from];
// [VALIDATION] Cooldown period and window checks (skip if in post-slashing period)
if (!inPostSlashingPeriod) {
require(
(block.timestamp >= cooldownSnapshot.timestamp + _cooldownSeconds),
'INSUFFICIENT_COOLDOWN'
);
require(
(block.timestamp - (cooldownSnapshot.timestamp + _cooldownSeconds) <=
UNSTAKE_WINDOW),
'UNSTAKE_WINDOW_FINISHED'
);
}
uint256 balanceOfFrom = balanceOf(from);
// Determine max redeemable amount
uint256 maxRedeemable = inPostSlashingPeriod
? balanceOfFrom
: cooldownSnapshot.amount;
require(maxRedeemable != 0, 'INVALID_ZERO_MAX_REDEEMABLE');
// Cap redemption to max redeemable
uint256 amountToRedeem = (amount > maxRedeemable) ? maxRedeemable : amount;
// Update rewards before burning
_updateCurrentUnclaimedRewards(from, balanceOfFrom, true);
// [TRANSFORMATION] Calculate underlying assets from shares
uint256 underlyingToRedeem = previewRedeem(amountToRedeem);
// Burn shares
_burn(from, amountToRedeem.toUint104());
// Update or delete cooldown state
if (cooldownSnapshot.timestamp != 0) {
if (cooldownSnapshot.amount - amountToRedeem == 0) {
delete stakersCooldowns[from];
} else {
stakersCooldowns[from].amount =
stakersCooldowns[from].amount -
amountToRedeem.toUint184();
}
}
// Transfer underlying assets to recipient
IERC20(STAKED_TOKEN).safeTransfer(to, underlyingToRedeem);
// [EVENT] Emit redeem event
emit Redeem(from, to, underlyingToRedeem, amountToRedeem);
}
```
### 5. Preview and Exchange Rate Functions
**File:** `StakedTokenV3.sol`
```solidity
function previewRedeem(uint256 shares) public view returns (uint256) {
return (EXCHANGE_RATE_UNIT * shares) / _currentExchangeRate;
}
function getExchangeRate() public view returns (uint216) {
return _currentExchangeRate;
}
```
---
## Amount Transformations
### Shares → Assets Calculation
```
User Input
↓
shares = 1100 * 10^18 // Amount of stkAAVE to redeem
↓
_currentExchangeRate = 1.1 * 10^18 // Current exchange rate (18 decimals)
↓
underlyingToRedeem = (EXCHANGE_RATE_UNIT * shares) / _currentExchangeRate
= (10^18 * 1100 * 10^18) / (1.1 * 10^18)
= 1000 * 10^18
↓
safeTransfer(to, underlyingToRedeem)
```
### Exchange Rate Mechanics
The exchange rate represents the ratio of shares to underlying assets:
**Exchange Rate Formula:**
```solidity
function previewRedeem(uint256 shares) public view returns (uint256) {
return (EXCHANGE_RATE_UNIT * shares) / _currentExchangeRate;
}
```
**Key Points:**
- `EXCHANGE_RATE_UNIT` = 10^18 (18 decimal precision)
- Higher exchange rate = more assets per share (rewards distributed)
- Lower exchange rate = fewer assets per share (slashing event)
### Cooldown Window Mechanics
```
Cooldown Started at T0
↓
Wait _cooldownSeconds (e.g., 10 days)
↓
Redemption Window Opens at T0 + _cooldownSeconds
↓
Redemption Window Closes at T0 + _cooldownSeconds + UNSTAKE_WINDOW
↓
Must redeem within UNSTAKE_WINDOW (e.g., 2 days) or restart cooldown
```
---
## Event Details
### Cooldown Event
Emitted when a user initiates the cooldown period for unstaking.
```solidity
event Cooldown(
address indexed user, // User who started the cooldown
uint256 amount // Amount of shares under cooldown (full balance)
);
```
### Redeem Event
Emitted when shares are successfully redeemed for underlying assets.
```solidity
event Redeem(
address indexed from, // Address redeeming shares
address indexed to, // Address receiving the underlying assets
uint256 assets, // Amount of AAVE tokens transferred
uint256 shares // Amount of stkAAVE shares burned
);
```
---
## Error Conditions
| Error | Condition | File |
|-------|-----------|------|
| `INVALID_BALANCE_ON_COOLDOWN` | `balanceOf(from) == 0` when calling cooldown | StakedTokenV3.sol |
| `INVALID_ZERO_AMOUNT` | `amount == 0` when redeeming | StakedTokenV3.sol |
| `INSUFFICIENT_COOLDOWN` | `block.timestamp < cooldownSnapshot.timestamp + _cooldownSeconds` | StakedTokenV3.sol |
| `UNSTAKE_WINDOW_FINISHED` | Redemption attempted after cooldown window expired | StakedTokenV3.sol |
| `INVALID_ZERO_MAX_REDEEMABLE` | No redeemable balance available (cooldown amount is 0) | StakedTokenV3.sol |
---
## Related Flows
- [stkAAVE Staking Flow](./stk_aave_staking.md) - Staking AAVE to receive stkAAVE
- [Rewards Claim Flow](./rewards_claiming.md) - Claiming staking rewards
- [Slashing Flow](./stk_aave_slashing.md) - Emergency slashing mechanism
---
## Source File Locations
```
contract_reference/aave/stkAAVE/rev_6.sol (StakedTokenV3 - main implementation)
├── cooldown()
├── cooldownOnBehalfOf(from)
├── redeem(to, amount)
├── redeemOnBehalf(from, to, amount)
├── _cooldown(from)
├── _redeem(from, to, amount)
├── previewRedeem(shares)
└── getExchangeRate()
```