# Repay Flow
End-to-end execution flow for repaying debt in Aave V3.
## Quick Reference
| Aspect | Details |
|--------|---------|
| **Entry Point** | `Pool.repay(asset, amount, interestRateMode, onBehalfOf)` |
| **Key Transformations** | [Scaled Debt → Amount](../transformations/index.md#debt-token-transformations) |
| **State Changes** | `_scaledBalance[onBehalfOf] -= scaledAmount` |
| **Events Emitted** | `Repay`, `IsolationModeTotalDebtUpdated` (conditional) |
---
## 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 point
Entry["Pool.repay
asset, amount,
interestRateMode,
onBehalfOf"] --> Execute["RepayLogic
executeRepay"]
subgraph StateUpdate ["1. State Updates"]
direction TB
UpdateState["ReserveLogic
updateState"] --> GetDebt["Helpers
getUserCurrentDebt
Gets stable + variable debt"]
end
subgraph Validation ["2. Validation"]
direction TB
GetDebt --> Validate["ValidationLogic
validateRepay
Checks: amount > 0"]
class Validate validation
end
subgraph Decision ["3. Repay Method Decision"]
direction TB
Validate --> ATokensCheck{"Use aTokens?"}
ATokensCheck -->|Yes| CalcFromAToken["TRANSFORMATION
paybackAmount =
aToken.balanceOf"]
class CalcFromAToken transformation
ATokensCheck -->|No| UseProvided["Use provided amount
or MAX_UINT for full"]
CalcFromAToken --> ModeCheck{"Interest
Rate Mode"}
UseProvided --> ModeCheck
end
subgraph TokenBurn ["4. Debt Token Burn"]
direction TB
ModeCheck -->|STABLE| StableBurn["StableDebtToken
burn"]
ModeCheck -->|VARIABLE| VariableBurn["VariableDebtToken
burn"]
StableBurn --> StableTransform["TRANSFORMATION
principal -= amount
+ accrued interest"]
class StableTransform transformation
VariableBurn --> VariableTransform["TRANSFORMATION
scaledAmountToBurn =
amount.rayDiv(index)"]
class VariableTransform transformation
StableTransform --> StoreStable["STORAGE UPDATE
_balances.principal -= amount"]
class StoreStable storage
VariableTransform --> StoreVariable["STORAGE UPDATE
_scaledBalance -=
scaledAmountToBurn"]
class StoreVariable storage
end
Execute --> StateUpdate
StateUpdate --> Validation
Validation --> Decision
StoreStable --> UpdateRates["ReserveLogic
updateInterestRates"]
StoreVariable --> UpdateRates
subgraph CollateralMgmt ["5. Collateral Management"]
direction TB
UpdateRates --> IsoLogic["IsolationModeLogic
updateIsolatedDebtIfIsolated"]
class IsoLogic storage
IsoLogic --> RepayTokenCheck{"Use aTokens?"}
RepayTokenCheck -->|Yes| BurnAToken["AToken
burn"]
RepayTokenCheck -->|No| TransferUnderlying["IERC20
safeTransferFrom"]
class TransferUnderlying storage
end
BurnAToken --> FinalEvent["EMIT
Repay"]
class FinalEvent event
TransferUnderlying --> FinalEvent
%% Error annotations
%% CRITICAL: Debt token burn is irreversible
%% CRITICAL: Isolation mode debt ceiling must be respected
%% CRITICAL: Repayment caps at current debt balance
%% Link styles for critical paths
linkStyle 18 stroke:#ff0000,stroke-width:3px
linkStyle 22 stroke:#ff0000,stroke-width:3px
```
---
## Step-by-Step Execution
### 1. Entry Point
**File:** `contracts/protocol/pool/Pool.sol`
```solidity
function repay(
address asset,
uint256 amount,
uint256 interestRateMode,
address onBehalfOf
) external virtual override returns (uint256) {
return BorrowLogic.executeRepay(
_reserves,
_reservesList,
_usersConfig[onBehalfOf],
DataTypes.ExecuteRepayParams({
asset: asset,
amount: amount,
interestRateMode: DataTypes.InterestRateMode(interestRateMode),
onBehalfOf: onBehalfOf,
useATokens: false,
user: msg.sender
})
);
}
```
### 2. Execute Repay
**File:** `contracts/protocol/libraries/logic/BorrowLogic.sol`
```solidity
function executeRepay(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteRepayParams 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 current debt
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(
params.onBehalfOf,
reserveCache
);
// Determine payback amount
uint256 paybackAmount = params.interestRateMode ==
DataTypes.InterestRateMode.STABLE
? stableDebt
: variableDebt;
// If using aTokens, calculate from balance
if (params.useATokens) {
paybackAmount = IAToken(reserveCache.aTokenAddress)
.balanceOf(msg.sender);
}
// Validate repayment
ValidationLogic.validateRepay(
reserveCache,
params.amount,
params.interestRateMode,
params.onBehalfOf,
stableDebt,
variableDebt
);
// Burn debt tokens
uint256 amountToRepay = params.amount;
if (amountToRepay == type(uint256).max) {
amountToRepay = paybackAmount;
}
if (params.interestRateMode == DataTypes.InterestRateMode.STABLE) {
IStableDebtToken(reserveCache.stableDebtTokenAddress).burn(
params.onBehalfOf,
amountToRepay
);
} else {
IVariableDebtToken(reserveCache.variableDebtTokenAddress).burn(
params.onBehalfOf,
amountToRepay,
reserveCache.nextVariableBorrowIndex
);
}
// Update interest rates
reserve.updateInterestRates(
reserveCache,
params.asset,
amountToRepay, // liquidityAdded
0 // liquidityTaken
);
// Update isolation mode debt if applicable
IsolationModeLogic.updateIsolatedDebtIfIsolated(
reserves,
reservesList,
userConfig,
reserveCache,
amountToRepay
);
// Transfer repayment
if (params.useATokens) {
IAToken(reserveCache.aTokenAddress).burn(
msg.sender,
reserveCache.aTokenAddress,
amountToRepay,
reserveCache.nextLiquidityIndex
);
} else {
IERC20(params.asset).safeTransferFrom(
msg.sender,
reserveCache.aTokenAddress,
amountToRepay
);
}
emit Repay(
params.asset,
params.onBehalfOf,
msg.sender,
amountToRepay,
params.useATokens
);
return amountToRepay;
}
```
### 3. Variable Debt Token Burn
**File:** `contracts/protocol/tokenization/VariableDebtToken.sol`
```solidity
function burn(
address from,
uint256 amount,
uint256 index
) external override onlyPool {
_burnScaled(from, amount, index);
}
function _burnScaled(address from, uint256 amount, uint256 index) internal {
uint256 scaledAmount = amount.rayDiv(index); // [TRANSFORMATION]
uint256 scaledBalanceBefore = _scaledBalance[from];
// Cap at balance
if (scaledAmount > scaledBalanceBefore) {
scaledAmount = scaledBalanceBefore;
amount = scaledAmount.rayMul(index);
}
_scaledBalance[from] -= scaledAmount;
}
```
**[TRANSFORMATION]:** See [Debt Token Transformations](../transformations/index.md#debt-token-transformations) for details on `amount.rayDiv(index)`
### 4. Stable Debt Token Burn
**File:** `contracts/protocol/tokenization/StableDebtToken.sol`
```solidity
function burn(address from, uint256 amount) external override onlyPool {
_burn(from, amount);
}
function _burn(address from, uint256 amount) internal {
// Calculate current principal + interest
uint256 accountBalance = _balances[from].principal;
uint256 balanceIncrease = accountBalance.rayMul(
MathUtils.calculateCompoundedInterest(
_balances[from].stableRate,
_balances[from].lastUpdateTimestamp
)
) - accountBalance;
uint256 amountToBurn = amount;
if (amount > accountBalance + balanceIncrease) {
amountToBurn = accountBalance + balanceIncrease;
}
_balances[from].principal = accountBalance + balanceIncrease - amountToBurn;
_balances[from].lastUpdateTimestamp = block.timestamp;
_totalSupply.principal -= amountToBurn;
_totalSupply.lastUpdateTimestamp = block.timestamp;
}
```
**Note:** Stable debt calculates interest at burn time based on elapsed time.
### 5. Validation Checks
**File:** `contracts/protocol/libraries/logic/ValidationLogic.sol`
```solidity
function validateRepay(
DataTypes.ReserveCache memory reserveCache,
uint256 amount,
DataTypes.InterestRateMode interestRateMode,
address onBehalfOf,
uint256 stableDebt,
uint256 variableDebt
) internal pure {
require(amount != 0, Errors.INVALID_AMOUNT);
// Check user has debt in this mode
if (interestRateMode == DataTypes.InterestRateMode.STABLE) {
require(stableDebt > 0, Errors.NO_DEBT_OF_SELECTED_TYPE);
} else {
require(variableDebt > 0, Errors.NO_DEBT_OF_SELECTED_TYPE);
}
}
```
### 6. Isolation Mode Debt Update
**File:** `contracts/protocol/libraries/logic/IsolationModeLogic.sol`
```solidity
function updateIsolatedDebtIfIsolated(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ReserveCache memory reserveCache,
uint256 repayAmount
) internal {
// Check if reserve is in isolation mode
if (reserveCache.reserveConfiguration.getDebtCeiling() != 0) {
DataTypes.ReserveData storage reserve = reserves[
reservesList[reserveCache.reserveConfiguration.getId()]
];
// Reduce isolation mode total debt
uint128 isolatedDebt = reserve.isolationModeTotalDebt;
uint256 repayAmountUint = repayAmount / 10**
(reserveCache.reserveConfiguration.getDecimals() -
ReserveConfiguration.DEBT_CEILING_DECIMALS);
if (repayAmountUint > isolatedDebt) {
reserve.isolationModeTotalDebt = 0;
} else {
reserve.isolationModeTotalDebt -= uint128(repayAmountUint);
}
emit IsolationModeTotalDebtUpdated(
reserveCache.aTokenAddress,
reserve.isolationModeTotalDebt
);
}
}
```
---
## Amount Transformations
### Variable Rate Repay
```
_scaledBalance[onBehalfOf].rayMul(index) = currentDebt (WAD)
↓
Validation: currentDebt > 0
↓
Calculate payback = min(requested, currentDebt)
↓
scaledAmountToBurn = payback.rayDiv(index) // v4+: uses rayDivFloor
↓
_scaledBalance[onBehalfOf] -= scaledAmountToBurn
```
### Stable Rate Repay
```
_principal = _balances[onBehalfOf].principal
_accruedInterest = _principal.rayMul(compoundedInterest) - _principal
currentDebt = _principal + _accruedInterest (WAD)
↓
Validation: currentDebt > 0
↓
payback = min(requested, currentDebt)
↓
_balances[onBehalfOf].principal = currentDebt - payback
_balances[onBehalfOf].lastUpdateTimestamp = block.timestamp
```
**Key Differences:**
- **Variable Rate:** Scaled balance approach, interest in index
- **Stable Rate:** Principal + timestamp, interest calculated on burn
- Repay can use underlying tokens OR aTokens
- Full debt repayment (amount=MAX) clears all debt in that mode
---
## Event Details
### Repay Event
```solidity
event Repay(
address indexed reserve, // Asset address
address indexed user, // Debt owner (onBehalfOf)
address indexed repayer, // msg.sender
uint256 amount, // Amount repaid
bool useATokens // True if repaid using aTokens
);
```
### IsolationModeTotalDebtUpdated Event
Emitted when repaying isolated debt.
```solidity
event IsolationModeTotalDebtUpdated(
address indexed asset,
uint256 totalDebt
);
```
---
## Error Conditions
| Error | Condition | File |
|-------|-----------|------|
| `INVALID_AMOUNT` | `amount == 0` | ValidationLogic.sol |
| `NO_DEBT_OF_SELECTED_TYPE` | Repaying stable but only has variable debt (or vice versa) | ValidationLogic.sol |
---
## Related Flows
- [Borrow Flow](./borrow.md) - Taking out debt
- [Liquidation Flow](./liquidation.md) - Debt repayment via liquidation
---
## Source File Locations
```
contracts/protocol/pool/Pool.sol
contracts/protocol/libraries/logic/BorrowLogic.sol
contracts/protocol/libraries/logic/ValidationLogic.sol
contracts/protocol/libraries/logic/IsolationModeLogic.sol
contracts/protocol/libraries/helpers/Helpers.sol
contracts/protocol/tokenization/VariableDebtToken.sol
contracts/protocol/tokenization/StableDebtToken.sol
contracts/protocol/tokenization/AToken.sol
contracts/protocol/libraries/logic/ReserveLogic.sol
```