Withdraw Flow¶
End-to-end execution flow for withdrawing assets from Aave V3.
Quick Reference¶
Aspect |
Details |
|---|---|
Entry Point |
|
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
classDef critical stroke:#ff0000,stroke-width:3px
%% Entry point
Entry["Pool.withdraw
asset, amount, to"] --> Execute["WithdrawLogic
executeWithdraw"]
subgraph StateUpdate ["1. State Updates"]
direction TB
UpdateState["ReserveLogic
updateState
Updates indexes,
accrues interest"] --> Validate["ValidationLogic
validateWithdraw"]
class Validate validation
end
subgraph TokenOps ["2. Token Operations"]
direction TB
GetBalance["TRANSFORMATION
userBalance =
scaledBalance
.rayMul(index)"] --> MaxCheck{"amount ==
MAX_UINT?"}
MaxCheck -->|Yes| UseMax["Set amountToWithdraw
= userBalance"]
MaxCheck -->|No| UseProvided["Use provided amount"]
UseMax --> Validate
UseProvided --> Validate
Validate --> UpdateRates["ReserveLogic
updateInterestRates"]
end
subgraph HealthCheck ["3. Health Factor / Safety Checks"]
direction TB
Burn["AToken
burn"] --> Transform["TRANSFORMATION
scaledAmount =
amount.rayDiv(index)"]
class Transform transformation
Transform --> Store["STORAGE UPDATE
_scaledBalance -=
scaledAmount"]
class Store storage
Store --> CollatCheck{"Is Collateral
&& amount ==
userBalance?"}
CollatCheck -->|Yes| DisableCollat["UserConfig
setUsingAsCollateral
(reserve.id, false)"]
class DisableCollat storage
DisableCollat --> CollatEvent["EMIT
ReserveUsedAsCollateralDisabled"]
class CollatEvent event
CollatCheck -->|No| SkipCollat[Continue]
CollatEvent --> HFCheck{"Is Collateral
&& Has Debt?"}
SkipCollat --> HFCheck
end
UpdateRates --> TokenOps
UpdateRates --> Burn
%% Critical path: Health factor validation
HFCheck -->|Yes| HFValidation["ValidationLogic
validateHFAndLtv
CRITICAL: HF >= 1.0"]
class HFValidation validation
class HFValidation critical
HFCheck -->|No| SkipHF[Continue]
HFValidation --> FinalEvent["EMIT
Withdraw"]
class FinalEvent event
SkipHF --> FinalEvent
%% Error annotations
%% CRITICAL: Health factor validation reverts if HF < 1.0
%% CRITICAL: Collateral disabled if withdrawing full balance
%% Link styles for critical paths
linkStyle 20 stroke:#ff0000,stroke-width:3px
Step-by-Step Execution¶
1. Entry Point¶
File: contracts/protocol/pool/Pool.sol
function withdraw(
address asset,
uint256 amount,
address to
) external virtual override returns (uint256) {
return SupplyLogic.executeWithdraw(
_reserves,
_reservesList,
_usersConfig[msg.sender],
DataTypes.ExecuteWithdrawParams({
asset: asset,
amount: amount,
to: to,
reservesCount: _reservesCount,
oracle: ADDRESSES_PROVIDER.getPriceOracle(),
userEModeCategory: _usersEModeCategory[msg.sender]
})
);
}
2. Execute Withdraw¶
File: contracts/protocol/libraries/logic/SupplyLogic.sol
function executeWithdraw(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteWithdrawParams memory params
) external returns (uint256) {
DataTypes.ReserveData storage reserve = reserves[params.asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
// Update state
reserve.updateState(reserveCache);
// Get user's balance
uint256 userBalance = IAToken(reserveCache.aTokenAddress)
.scaledBalanceOf(msg.sender)
.rayMul(reserveCache.nextLiquidityIndex); // [TRANSFORMATION]
// Handle max withdrawal
uint256 amountToWithdraw = params.amount;
if (params.amount == type(uint256).max) {
amountToWithdraw = userBalance;
}
// Validate withdrawal
ValidationLogic.validateWithdraw(
reserves,
reservesList,
reserveCache,
amountToWithdraw,
userConfig
);
// Update interest rates
reserve.updateInterestRates(
reserveCache,
params.asset,
0, // liquidityAdded
amountToWithdraw // liquidityTaken
);
// Burn aTokens
IAToken(reserveCache.aTokenAddress).burn(
msg.sender,
params.to,
amountToWithdraw,
reserveCache.nextLiquidityIndex
);
// Check if collateral should be disabled
if (userConfig.isUsingAsCollateral(reserve.id)) {
if (amountToWithdraw == userBalance) {
userConfig.setUsingAsCollateral(reserve.id, false);
emit ReserveUsedAsCollateralDisabled(params.asset, msg.sender);
}
// Validate health factor if user has debt
if (userConfig.isBorrowingAny()) {
ValidationLogic.validateHFAndLtv(
reserves,
reservesList,
userConfig,
params.asset,
params.userEModeCategory,
params.reservesCount,
params.oracle
);
}
}
emit Withdraw(
params.asset,
msg.sender,
params.to,
amountToWithdraw
);
return amountToWithdraw;
}
3. AToken Burn¶
File: contracts/protocol/tokenization/AToken.sol
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyPool {
_burnScaled(from, receiverOfUnderlying, amount, index);
}
function _burnScaled(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) internal {
uint256 scaledAmount = amount.rayDiv(index); // [TRANSFORMATION]
_scaledBalance[from] -= scaledAmount;
IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount);
}
[TRANSFORMATION]: See Collateral Token Transformations for details on amount.rayDiv(index)
4. Validation Checks¶
File: contracts/protocol/libraries/logic/ValidationLogic.sol
function validateWithdraw(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.ReserveCache memory reserveCache,
uint256 amount,
DataTypes.UserConfigurationMap storage userConfig
) internal view {
require(amount != 0, Errors.INVALID_AMOUNT);
// Check reserve is active
require(
reserveCache.reserveConfiguration.getActive(),
Errors.RESERVE_INACTIVE
);
// Check user has sufficient balance
uint256 userBalance = IAToken(reserveCache.aTokenAddress)
.scaledBalanceOf(msg.sender)
.rayMul(reserveCache.nextLiquidityIndex);
require(userBalance >= amount, Errors.INVALID_AMOUNT);
}
5. Health Factor Check¶
File: contracts/protocol/libraries/logic/ValidationLogic.sol
function validateHFAndLtv(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
address asset,
uint8 userEModeCategory,
uint256 reservesCount,
address oracle
) internal view {
// Calculate user account data
(
uint256 totalCollateralInBaseCurrency,
uint256 totalDebtInBaseCurrency,
uint256 avgLtv,
uint256 avgLiquidationThreshold,
uint256 healthFactor,
bool hasZeroLtvCollateral
) = GenericLogic.calculateUserAccountData(
reserves,
reservesList,
_eModeCategories,
DataTypes.CalculateUserAccountDataParams({
userConfig: userConfig,
reservesCount: reservesCount,
user: msg.sender,
oracle: oracle,
userEModeCategory: userEModeCategory
})
);
// Check health factor
require(
healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
// Check LTV if withdrawing 0 LTV collateral
if (hasZeroLtvCollateral && totalDebtInBaseCurrency > 0) {
DataTypes.ReserveData storage collateralReserve = reserves[asset];
DataTypes.ReserveCache memory collateralReserveCache = collateralReserve.cache();
require(
collateralReserveCache.reserveConfiguration.getLtv() != 0 || // Not withdrawing 0-LTV asset
avgLtv > 0, // Or user has other collateral with LTV > 0
Errors.LTV_VALIDATION_FAILED
);
}
}
Amount Transformations¶
Storage → Output¶
_scaledBalance[msg.sender] (stored scaled)
↓
liquidityIndex = 1.0002 * 10^27 // Current index
↓
userBalance = _scaledBalance[msg.sender].rayMul(liquidityIndex)
= 1000 * 10^18 // 1000 tokens + accrued interest
↓
Validation: amount <= userBalance
↓
scaledAmountToBurn = amount.rayDiv(liquidityIndex)
= (1000 * 10^18 * 10^27) / (1.0002 * 10^27)
↓
_scaledBalance[msg.sender] -= scaledAmountToBurn
↓
Transfer amount to user
Key Points:
User receives WAD-decimal amount (18 decimals)
Withdrawn amount includes accrued interest from scaled balance
Health factor must remain above liquidation threshold
If withdrawing all collateral and user has debt, HF check is strict
Event Details¶
Withdraw Event¶
event Withdraw(
address indexed reserve, // Asset address
address indexed user, // msg.sender
address indexed to, // Recipient of underlying
uint256 amount // Amount withdrawn
);
ReserveUsedAsCollateralDisabled Event¶
Emitted when withdrawing all of a collateral Asset.
event ReserveUsedAsCollateralDisabled(
address indexed reserve,
address indexed user
);
Error Conditions¶
Error |
Condition |
File |
|---|---|---|
|
|
ValidationLogic.sol |
|
Reserve is not active |
ValidationLogic.sol |
|
HF < 1.0 after withdrawal |
ValidationLogic.sol |
|
Withdrawing 0-LTV collateral while having debt |
ValidationLogic.sol |
Source File Locations¶
contracts/protocol/pool/Pool.sol
contracts/protocol/libraries/logic/SupplyLogic.sol
contracts/protocol/libraries/logic/ValidationLogic.sol
contracts/protocol/libraries/logic/GenericLogic.sol
contracts/protocol/tokenization/AToken.sol
contracts/protocol/libraries/logic/ReserveLogic.sol