# stkAAVE Slashing Flow
End-to-end execution flow for the stkAAVE emergency slashing mechanism. Slashing is a security feature that allows the slashing admin to seize a percentage of staked funds in case of protocol insolvency or emergency scenarios.
## Quick Reference
| Aspect | Details |
|--------|---------|
| **Entry Points** | `slash(destination, amount)`, `settleSlashing()`, `returnFunds(amount)` |
| **Key Transformations** | [Slashing Amount Calculation](#amount-transformations), [Exchange Rate Adjustment](#amount-transformations) |
| **State Changes** | `inPostSlashingPeriod = true`, `_currentExchangeRate` decreased, `_totalSupply` unchanged |
| **Events Emitted** | `Slashed`, `SlashingSettled`, `ExchangeRateChanged`, `FundsReturned` |
---
## 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
Slash["slash(destination, amount)"] --> ValidateSlash1["VALIDATION
!inPostSlashingPeriod
amount > 0
onlySlashingAdmin"]
class ValidateSlash1 validation
Settle["settleSlashing()"] --> ValidateSettle["VALIDATION
onlySlashingAdmin"]
class ValidateSettle validation
Return["returnFunds(amount)"] --> ValidateReturn["VALIDATION
amount >= LOWER_BOUND
totalSupply >= LOWER_BOUND"]
class ValidateReturn validation
%% Slash flow
ValidateSlash1 --> CalcBalance["Get total assets
previewRedeem(totalShares)"]
CalcBalance --> CalcMaxSlash["TRANSFORMATION
maxSlashable =
balance.percentMul
_maxSlashablePercentage"]
class CalcMaxSlash transformation
CalcMaxSlash --> CheckAmount{"amount >
maxSlashable?"}
CheckAmount -->|Yes| CapAmount["amount = maxSlashable"]
CheckAmount -->|No| KeepAmount["Keep requested amount"]
CapAmount --> CheckRemaining["VALIDATION
balance - amount >= LOWER_BOUND"]
KeepAmount --> CheckRemaining
class CheckRemaining validation
CheckRemaining --> SetSlashingFlag["STORAGE UPDATE
inPostSlashingPeriod = true"]
class SetSlashingFlag storage
SetSlashingFlag --> CalcNewRate["TRANSFORMATION
newExchangeRate =
_getExchangeRate
balance - amount,
currentShares"]
class CalcNewRate transformation
CalcNewRate --> UpdateRate["STORAGE UPDATE
_updateExchangeRate
newExchangeRate"]
class UpdateRate storage
UpdateRate --> Transfer["IERC20
safeTransfer
destination"]
Transfer --> EmitSlashed["EMIT
Slashed"]
class EmitSlashed event
EmitSlashed --> ReturnAmount["Return slashed amount"]
%% Settle flow
ValidateSettle --> ClearFlag["STORAGE UPDATE
inPostSlashingPeriod = false"]
class ClearFlag storage
ClearFlag --> EmitSettled["EMIT
SlashingSettled"]
class EmitSettled event
%% Return funds flow
ValidateReturn --> GetShares["Get total shares
totalSupply()"]
GetShares --> GetAssets["Get total assets
previewRedeem(shares)"]
GetAssets --> CalcReturnRate["TRANSFORMATION
newExchangeRate =
_getExchangeRate
assets + amount,
shares"]
class CalcReturnRate transformation
CalcReturnRate --> UpdateReturnRate["STORAGE UPDATE
_updateExchangeRate
newExchangeRate"]
class UpdateReturnRate storage
UpdateReturnRate --> TransferFrom["IERC20
safeTransferFrom
msg.sender -> contract"]
TransferFrom --> EmitReturned["EMIT
FundsReturned"]
class EmitReturned event
%% Post-slashing effects
CheckRemaining -.->|Blocks| Staking["New stakes blocked
until settled"]
CheckRemaining -.->|Affects| Redeem["Redemption limited
during slashing period"]
%% Error annotations
%% CRITICAL: Slashing reduces exchange rate - all stakers share the loss proportionally
%% CRITICAL: No slashing during ongoing slashing period - must settle first
%% CRITICAL: Max slashable is capped at _maxSlashablePercentage (governance controlled)
```
---
## Step-by-Step Execution
### 1. Slashing Execution
**File:** `StakedTokenV3.sol`
```solidity
function slash(
address destination,
uint256 amount
) external override onlySlashingAdmin returns (uint256) {
// [VALIDATION] Cannot slash while another slashing event is ongoing
require(!inPostSlashingPeriod, 'PREVIOUS_SLASHING_NOT_SETTLED');
require(amount > 0, 'ZERO_AMOUNT');
// Get current shares and underlying assets
uint256 currentShares = totalSupply();
uint256 balance = previewRedeem(currentShares);
// [TRANSFORMATION] Calculate maximum slashable amount based on percentage cap
uint256 maxSlashable = balance.percentMul(_maxSlashablePercentage);
// Cap amount to max slashable if exceeded
if (amount > maxSlashable) {
amount = maxSlashable;
}
// [VALIDATION] Ensure remaining balance stays above minimum threshold
require(balance - amount >= LOWER_BOUND, 'REMAINING_LT_MINIMUM');
// [STORAGE UPDATE] Mark slashing period as active
inPostSlashingPeriod = true;
// [TRANSFORMATION] Calculate new exchange rate after slashing
_updateExchangeRate(_getExchangeRate(balance - amount, currentShares));
// Transfer slashed funds to destination
STAKED_TOKEN.safeTransfer(destination, amount);
// [EVENT] Emit slashing event
emit Slashed(destination, amount);
return amount;
}
```
### 2. Settling Slashing
**File:** `StakedTokenV3.sol`
```solidity
function settleSlashing() external override onlySlashingAdmin {
// [STORAGE UPDATE] Clear the slashing period flag
inPostSlashingPeriod = false;
// [EVENT] Emit settlement event
emit SlashingSettled();
}
```
### 3. Returning Funds
**File:** `StakedTokenV3.sol`
```solidity
function returnFunds(uint256 amount) external override {
// [VALIDATION] Minimum amount to prevent spam and exchange rate manipulation
require(amount >= LOWER_BOUND, 'AMOUNT_LT_MINIMUM');
uint256 currentShares = totalSupply();
require(currentShares >= LOWER_BOUND, 'SHARES_LT_MINIMUM');
// Get current total assets
uint256 assets = previewRedeem(currentShares);
// [TRANSFORMATION] Calculate new exchange rate with returned funds
_updateExchangeRate(_getExchangeRate(assets + amount, currentShares));
// Pull funds from caller to contract
STAKED_TOKEN.safeTransferFrom(msg.sender, address(this), amount);
// [EVENT] Emit funds returned event
emit FundsReturned(amount);
}
```
### 4. Exchange Rate Calculation
**File:** `StakedTokenV3.sol`
```solidity
function _getExchangeRate(uint256 totalAssets, uint256 totalShares)
internal pure returns (uint216)
{
// Rounds UP to ensure 100% backing of shares
return (((totalShares * EXCHANGE_RATE_UNIT) + totalAssets - 1) / totalAssets);
}
function _updateExchangeRate(uint216 newExchangeRate) internal {
_currentExchangeRate = newExchangeRate;
emit ExchangeRateChanged(newExchangeRate);
}
```
### 5. Max Slashable Percentage Management
**File:** `StakedTokenV3.sol`
```solidity
function setMaxSlashablePercentage(uint256 percentage)
external override onlySlashingAdmin
{
_setMaxSlashablePercentage(percentage);
}
function _setMaxSlashablePercentage(uint256 percentage) internal {
// [VALIDATION] Must be strictly less than 100% to avoid division by zero
require(
percentage < PercentageMath.PERCENTAGE_FACTOR,
'INVALID_SLASHING_PERCENTAGE'
);
_maxSlashablePercentage = percentage;
emit MaxSlashablePercentageChanged(percentage);
}
```
---
## Amount Transformations
### Slashing Amount Calculation
```
User Request
↓
amount = requested slashing amount
↓
currentShares = totalSupply()
balance = previewRedeem(currentShares) = (EXCHANGE_RATE_UNIT * shares) / _currentExchangeRate
↓
_maxSlashablePercentage = governance controlled (e.g., 3000 = 30%)
↓
maxSlashable = balance.percentMul(_maxSlashablePercentage)
= (balance * _maxSlashablePercentage) / PERCENTAGE_FACTOR (10000)
↓
if (amount > maxSlashable):
amount = maxSlashable
↓
require(balance - amount >= LOWER_BOUND) // Typically 10^18 (1 token)
↓
Return: actual amount to be slashed
```
### Exchange Rate Adjustment During Slashing
```
Before Slashing:
totalAssets = balance
totalShares = currentShares
_currentExchangeRate = (totalShares * 1e18 + totalAssets - 1) / totalAssets
After Slashing:
remainingAssets = balance - amount
totalShares = unchanged (no shares burned)
newExchangeRate = (totalShares * 1e18 + remainingAssets - 1) / remainingAssets
Example:
- totalShares = 1000e18
- balance = 1100e18 (1000 assets + 100 rewards)
- currentRate = (1000e18 * 1e18 + 1100e18 - 1) / 1100e18 ≈ 0.909e18
- Slash 100e18 → remainingAssets = 1000e18
- newRate = (1000e18 * 1e18 + 1000e18 - 1) / 1000e18 = 1e18
Impact: Share value decreases proportionally
Before: 1 share = 1.1 assets
After: 1 share = 1.0 assets
```
### Exchange Rate Adjustment During Fund Return
```
Return Scenario (recovering from slashing):
totalShares = currentShares (unchanged)
currentAssets = previewRedeem(totalShares)
newAssets = currentAssets + amount
newExchangeRate = (totalShares * 1e18 + newAssets - 1) / newAssets
Example:
- totalShares = 1000e18
- currentAssets = 1000e18 (after slashing)
- currentRate = 1e18
- Return 50e18 → newAssets = 1050e18
- newRate = (1000e18 * 1e18 + 1050e18 - 1) / 1050e18 ≈ 0.952e18
Impact: Share value increases
Before: 1 share = 1.0 assets
After: 1 share = 1.05 assets
```
**Key Points:**
- `EXCHANGE_RATE_UNIT` = 10^18 (18 decimal precision)
- `PERCENTAGE_FACTOR` = 10^4 (2 decimal percentage precision)
- Exchange rate rounds **up** to ensure 100% backing of shares (favors the contract)
- Slashing decreases exchange rate (dilutes all stakers proportionally)
- Returning funds increases exchange rate (restores value to all stakers)
- No shares are burned during slashing - all stakers share the loss proportionally
---
## Event Details
### Slashed Event
Emitted when funds are successfully slashed and transferred to the destination.
```solidity
event Slashed(
address indexed destination, // Address receiving the slashed funds
uint256 amount // Amount of tokens slashed
);
```
### SlashingSettled Event
Emitted when the slashing admin settles an ongoing slashing event, re-enabling staking.
```solidity
event SlashingSettled();
```
### ExchangeRateChanged Event
Emitted whenever the exchange rate is updated (during slashing or fund returns).
```solidity
event ExchangeRateChanged(
uint216 exchangeRate // New exchange rate (18 decimal precision)
);
```
### FundsReturned Event
Emitted when funds are returned to the contract after a slashing event.
```solidity
event FundsReturned(
uint256 amount // Amount of tokens returned
);
```
### MaxSlashablePercentageChanged Event
Emitted when the governance changes the maximum slashable percentage.
```solidity
event MaxSlashablePercentageChanged(
uint256 newPercentage // New maximum slashable percentage (2 decimal precision)
);
```
---
## Error Conditions
| Error | Condition | File |
|-------|-----------|------|
| `PREVIOUS_SLASHING_NOT_SETTLED` | `inPostSlashingPeriod == true` when calling slash | StakedTokenV3.sol |
| `ZERO_AMOUNT` | `amount == 0` in slash | StakedTokenV3.sol |
| `REMAINING_LT_MINIMUM` | `balance - amount < LOWER_BOUND` after slash | StakedTokenV3.sol |
| `AMOUNT_LT_MINIMUM` | `amount < LOWER_BOUND` in returnFunds | StakedTokenV3.sol |
| `SHARES_LT_MINIMUM` | `totalSupply() < LOWER_BOUND` in returnFunds | StakedTokenV3.sol |
| `CALLER_NOT_SLASHING_ADMIN` | `msg.sender != getAdmin(SLASH_ADMIN_ROLE)` | StakedTokenV3.sol |
| `CALLER_NOT_COOLDOWN_ADMIN` | `msg.sender != getAdmin(COOLDOWN_ADMIN_ROLE)` | StakedTokenV3.sol |
| `INVALID_SLASHING_PERCENTAGE` | `percentage >= PERCENTAGE_FACTOR (100%)` | StakedTokenV3.sol |
| `SLASHING_ONGOING` | `inPostSlashingPeriod == true` when attempting to stake | StakedTokenV3.sol |
---
## Permission Requirements
### Roles
The slashing mechanism uses a role-based access control system:
| Role | ID | Permissions |
|------|-----|-------------|
| `SLASH_ADMIN_ROLE` | 0 | `slash()`, `settleSlashing()`, `setMaxSlashablePercentage()` |
| `COOLDOWN_ADMIN_ROLE` | 1 | `setCooldownSeconds()` |
| `CLAIM_HELPER_ROLE` | 2 | `claimRewardsOnBehalf()`, `claimRewardsAndRedeemOnBehalf()` |
### Slashing Admin
- Set during contract initialization via `_initialize()`
- Can be changed using the RoleManager pattern:
1. Current admin calls `setPendingAdmin(SLASH_ADMIN_ROLE, newAdmin)`
2. New admin calls `claimRoleAdmin(SLASH_ADMIN_ROLE)` to accept
---
## Related Flows
- [Staking Flow](./stk_aave_staking.md) - Staking AAVE to receive stkAAVE (blocked during slashing)
- [Redeem Flow](./stk_aave_unstaking.md) - Unstaking stkAAVE to receive AAVE
- [Cooldown Flow](./stk_aave_unstaking.md) - Initiating the unstaking cooldown period
- [Rewards Claim Flow](./rewards_claiming.md) - Claiming staking rewards
---
## Source File Locations
```
contract_reference/aave/stkAAVE/rev_6.sol (StakedTokenV3 - slashing implementation)
├── slash(destination, amount)
├── settleSlashing()
├── returnFunds(amount)
├── setMaxSlashablePercentage(percentage)
├── getMaxSlashablePercentage()
└── inPostSlashingPeriod (state variable)
Inherited from RoleManager:
├── getAdmin(role)
├── setPendingAdmin(role, newPendingAdmin)
├── claimRoleAdmin(role)
└── SLASH_ADMIN_ROLE constant
```
---
## Security Considerations
### Slashing as Emergency Mechanism
1. **Purpose**: Slashing is designed as an emergency security mechanism to protect the protocol in case of insolvency or critical security issues.
2. **Impact on Stakers**: All stakers share the loss proportionally since no shares are burned - the exchange rate simply decreases.
3. **Governance Control**:
- The maximum slashable percentage is capped by `_maxSlashablePercentage` (e.g., 30%)
- Only the slashing admin can execute slashing
- Slashing admin is typically the Aave governance executor or a security council
4. **Post-Slashing Period**:
- New stakes are blocked until `settleSlashing()` is called
- Redemptions may be limited during the slashing period
- The slashing period flag prevents multiple simultaneous slashing events
5. **Fund Recovery**:
- `returnFunds()` is permissionless - anyone can return funds to restore value
- Returns increase the exchange rate, benefiting all remaining stakers
- Minimum amounts prevent spam attacks on the exchange rate
---
## State Variables
```solidity
// Flag indicating active slashing period
bool public inPostSlashingPeriod;
// Maximum percentage of staked funds that can be slashed (2 decimal precision)
uint256 internal _maxSlashablePercentage;
// Current exchange rate (18 decimal precision)
uint216 internal _currentExchangeRate;
// Minimum token amount to prevent spam (10^decimals)
uint256 public immutable LOWER_BOUND;
// Exchange rate precision unit
uint216 public constant EXCHANGE_RATE_UNIT = 1e18;
// Slashing admin role identifier
uint256 public constant SLASH_ADMIN_ROLE = 0;
```
---
## Example Scenarios
### Scenario 1: Normal Slashing
```
Initial State:
- Total shares: 1,000,000 stkAAVE
- Total assets: 1,100,000 AAVE (includes rewards)
- Exchange rate: 1.1e18
- Max slashable: 30% (3000 basis points)
Slashing Execution:
- Request: slash(treasury, 500,000 AAVE)
- Max allowed: 1,100,000 * 0.30 = 330,000 AAVE
- Actual slashed: 330,000 AAVE (capped to max)
- Remaining assets: 770,000 AAVE
- New exchange rate: (1,000,000 * 1e18 + 770,000 - 1) / 770,000 ≈ 1.299e18
Result:
- Each share was worth 1.1 AAVE, now worth ~0.77 AAVE
- All stakers lost ~30% of their staked value proportionally
```
### Scenario 2: Fund Recovery
```
Post-Slashing State:
- Total shares: 1,000,000 stkAAVE
- Total assets: 770,000 AAVE
- Exchange rate: ~1.299e18
Recovery Execution:
- Treasury returns: returnFunds(100,000 AAVE)
- New total assets: 870,000 AAVE
- New exchange rate: (1,000,000 * 1e18 + 870,000 - 1) / 870,000 ≈ 1.149e18
Result:
- Each share value increases from ~0.77 to ~0.87 AAVE
- Value restored proportionally to all stakers
```