# Repay With aTokens Flow
End-to-end execution flow for repaying debt using aTokens instead of underlying Tokens in Aave V3.
## Quick Reference
| Aspect | Details |
|--------|---------|
| **Entry Point** | `Pool.repayWithATokens(asset, amount, interestRateMode)` |
| **Key Transformations** | [Scaled Debt → Amount](../transformations/index.md#debt-token-transformations), [aToken Balance → Payback Amount](../transformations/index.md#collateral-token-transformations) |
| **State Changes** | `_scaledBalance[msg.sender] -= scaledAmount`, burns aTokens from caller |
| **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.repayWithATokens
asset, amount,
interestRateMode"] --> Execute["BorrowLogic
executeRepay
useATokens: true"]
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, debt exists"]
class Validate validation
end
subgraph AmountCalc ["3. Amount Calculation"]
direction TB
Validate --> MaxCheck{"amount ==
MAX_UINT?"}
MaxCheck -->|Yes| UseATokenBalance["TRANSFORMATION
paybackAmount =
aToken.balanceOf
msg.sender"]
class UseATokenBalance transformation
MaxCheck -->|No| UseProvided["Use provided amount"]
UseATokenBalance --> CapCheck["Cap at
current debt"]
UseProvided --> CapCheck
end
subgraph TokenBurn ["4. Debt Token Burn"]
direction TB
CapCheck --> ModeCheck{"Interest
Rate Mode"}
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 --> AmountCalc
StoreStable --> UpdateRates["ReserveLogic
updateInterestRates
liquidityAdded: 0"]
StoreVariable --> UpdateRates
subgraph CollateralMgmt ["5. Collateral Management"]
direction TB
UpdateRates --> IsoLogic["IsolationModeLogic
updateIsolatedDebtIfIsolated"]
class IsoLogic storage
IsoLogic --> BurnAToken["AToken
burn
from: msg.sender"]
class BurnAToken storage
end
BurnAToken --> FinalEvent["EMIT
Repay
useATokens: true"]
class FinalEvent event
%% 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 repayWithATokens(
address asset,
uint256 amount,
uint256 interestRateMode
) public virtual override returns (uint256) {
return
BorrowLogic.executeRepay(
_reserves,
_reservesList,
_usersConfig[msg.sender],
DataTypes.ExecuteRepayParams({
asset: asset,
amount: amount,
interestRateMode: DataTypes.InterestRateMode(interestRateMode),
onBehalfOf: msg.sender,
useATokens: true
})
);
}
```
**Key Differences from `repay()`:**
- **onBehalfOf**: Always `msg.sender` (cannot repay on behalf of others)
- **useATokens**: Set to `true` (uses aTokens instead of underlying)
### 2. Execute Repay
**File:** `contracts/protocol/libraries/logic/BorrowLogic.sol`
```solidity
function executeRepay(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteRepayParams memory params
) external returns (uint256) {
DataTypes.ReserveData storage reserve = reservesData[params.asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
reserve.updateState(reserveCache);
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(
params.onBehalfOf,
reserveCache
);
ValidationLogic.validateRepay(
reserveCache,
params.amount,
params.interestRateMode,
params.onBehalfOf,
stableDebt,
variableDebt
);
uint256 paybackAmount = params.interestRateMode == DataTypes.InterestRateMode.STABLE
? stableDebt
: variableDebt;
// Allows a user to repay with aTokens without leaving dust from interest.
if (params.useATokens && params.amount == type(uint256).max) {
params.amount = IAToken(reserveCache.aTokenAddress).balanceOf(msg.sender);
}
if (params.amount < paybackAmount) {
paybackAmount = params.amount;
}
// Burn debt tokens
if (params.interestRateMode == DataTypes.InterestRateMode.STABLE) {
IStableDebtToken(reserveCache.stableDebtTokenAddress).burn(
params.onBehalfOf,
paybackAmount
);
} else {
IVariableDebtToken(reserveCache.variableDebtTokenAddress).burn(
params.onBehalfOf,
paybackAmount,
reserveCache.nextVariableBorrowIndex
);
}
// Update interest rates - note: liquidityAdded is 0 when using aTokens
reserve.updateInterestRates(
reserveCache,
params.asset,
params.useATokens ? 0 : paybackAmount,
0
);
// Clear borrowing flag if all debt repaid
if (stableDebt + variableDebt - paybackAmount == 0) {
userConfig.setBorrowing(reserve.id, false);
}
// Update isolation mode debt if applicable
IsolationModeLogic.updateIsolatedDebtIfIsolated(
reservesData,
reservesList,
userConfig,
reserveCache,
paybackAmount
);
// Burn aTokens from caller (instead of transferring underlying)
if (params.useATokens) {
IAToken(reserveCache.aTokenAddress).burn(
msg.sender,
reserveCache.aTokenAddress,
paybackAmount,
reserveCache.nextLiquidityIndex
);
} else {
IERC20(params.asset).safeTransferFrom(
msg.sender,
reserveCache.aTokenAddress,
paybackAmount
);
IAToken(reserveCache.aTokenAddress).handleRepayment(
msg.sender,
params.onBehalfOf,
paybackAmount
);
}
emit Repay(
params.asset,
params.onBehalfOf,
msg.sender,
paybackAmount,
params.useATokens
);
return paybackAmount;
}
```
### 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);
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 {
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;
}
```
### 5. AToken Burn
**File:** `contracts/protocol/tokenization/AToken.sol`
```solidity
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external virtual override onlyPool {
_burnScaled(from, receiverOfUnderlying, amount, index);
}
function _burnScaled(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) internal {
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.INVALID_BURN_AMOUNT);
_scaledBalance[from] -= amountScaled;
}
```
**[TRANSFORMATION]:** See [Collateral Token Transformations](../transformations/index.md#collateral-token-transformations) for details on `amount.rayDiv(index)`
### 6. Validation Checks
**File:** `contracts/protocol/libraries/logic/ValidationLogic.sol`
```solidity
function validateRepay(
DataTypes.ReserveCache memory reserveCache,
uint256 amountSent,
DataTypes.InterestRateMode interestRateMode,
address onBehalfOf,
uint256 stableDebt,
uint256 variableDebt
) internal view {
require(amountSent != 0, Errors.INVALID_AMOUNT);
require(
amountSent != type(uint256).max || msg.sender == onBehalfOf,
Errors.NO_EXPLICIT_AMOUNT_TO_REPAY_ON_BEHALF
);
(bool isActive, , , , bool isPaused) = reserveCache.reserveConfiguration.getFlags();
require(isActive, Errors.RESERVE_INACTIVE);
require(!isPaused, Errors.RESERVE_PAUSED);
require(
(stableDebt != 0 && interestRateMode == DataTypes.InterestRateMode.STABLE) ||
(variableDebt != 0 && interestRateMode == DataTypes.InterestRateMode.VARIABLE),
Errors.NO_DEBT_OF_SELECTED_TYPE
);
}
```
### 7. Isolation Mode Debt Update
**File:** `contracts/protocol/libraries/logic/IsolationModeLogic.sol`
```solidity
function updateIsolatedDebtIfIsolated(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ReserveCache memory reserveCache,
uint256 repayAmount
) internal {
(bool isolationModeActive, address isolationModeCollateralAddress, ) = userConfig
.getIsolationModeState(reservesData, reservesList);
if (isolationModeActive) {
uint128 isolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
.isolationModeTotalDebt;
uint128 isolatedDebtRepaid = (repayAmount /
10 **
(reserveCache.reserveConfiguration.getDecimals() -
ReserveConfiguration.DEBT_CEILING_DECIMALS)).toUint128();
if (isolationModeTotalDebt <= isolatedDebtRepaid) {
reservesData[isolationModeCollateralAddress].isolationModeTotalDebt = 0;
emit IsolationModeTotalDebtUpdated(isolationModeCollateralAddress, 0);
} else {
uint256 nextIsolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
.isolationModeTotalDebt = isolationModeTotalDebt - isolatedDebtRepaid;
emit IsolationModeTotalDebtUpdated(
isolationModeCollateralAddress,
nextIsolationModeTotalDebt
);
}
}
}
```
---
## Amount Transformations
### Variable Rate Repay With aTokens
```
_scaledBalance[msg.sender].rayMul(index) = currentDebt (WAD)
|
aTokenBalance = _scaledBalance[msg.sender].rayMul(liquidityIndex) (WAD)
|
Validation: currentDebt > 0
|
IF amount == MAX_UINT:
paybackAmount = aTokenBalance
ELSE:
paybackAmount = min(requested, currentDebt, aTokenBalance)
|
scaledAmountToBurn = paybackAmount.rayDiv(index)
|
aTokenScaledAmount = paybackAmount.rayDiv(liquidityIndex)
|
_scaledBalanceDebt[msg.sender] -= scaledAmountToBurn
_scaledBalanceAToken[msg.sender] -= aTokenScaledAmount
```
### Stable Rate Repay With aTokens
```
_principal = _balances[msg.sender].principal
_accruedInterest = _principal.rayMul(compoundedInterest) - _principal
currentDebt = _principal + _accruedInterest (WAD)
|
aTokenBalance = _scaledBalance[msg.sender].rayMul(liquidityIndex) (WAD)
|
IF amount == MAX_UINT:
paybackAmount = min(aTokenBalance, currentDebt)
ELSE:
paybackAmount = min(requested, currentDebt, aTokenBalance)
|
_balances[msg.sender].principal = currentDebt - paybackAmount
_balances[msg.sender].lastUpdateTimestamp = block.timestamp
_scaledBalanceAToken[msg.sender] -= paybackAmount.rayDiv(liquidityIndex)
```
**Key Differences from Regular Repay:**
- **No underlying token transfer** - aTokens are burned directly
- **Interest rate update** uses `liquidityAdded: 0` since no new liquidity enters
- **Self-only repayment** - can only repay own debt
- **MAX_UINT handling** - uses aToken balance instead of debt amount
---
## Event Details
### Repay Event
```solidity
event Repay(
address indexed reserve,
address indexed user,
address indexed repayer,
uint256 amount,
bool useATokens
);
```
### IsolationModeTotalDebtUpdated Event
```solidity
event IsolationModeTotalDebtUpdated(
address indexed asset,
uint256 totalDebt
);
```
---
## Error Conditions
| Error | Condition | File |
|-------|-----------|------|
| `INVALID_AMOUNT` | `amount == 0` | ValidationLogic.sol |
| `NO_EXPLICIT_AMOUNT_TO_REPAY_ON_BEHALF` | `amount == MAX_UINT` when repaying on behalf of another user | ValidationLogic.sol |
| `RESERVE_INACTIVE` | Reserve is not active | ValidationLogic.sol |
| `RESERVE_PAUSED` | Reserve is paused | ValidationLogic.sol |
| `NO_DEBT_OF_SELECTED_TYPE` | Repaying stable but only has variable debt (or vice versa) | ValidationLogic.sol |
---
## Related Flows
- [Repay Flow](./repay.md) - Repaying with underlying tokens
- [Borrow Flow](./borrow.md) - Taking out debt
- [Liquidation Flow](./liquidation.md) - Debt repayment via liquidation
- [Supply Flow](./supply.md) - Acquiring aTokens to repay with
---
## 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
```