GHO Borrowing Flow¶
End-to-end execution flow for borrowing GHO (Aave’s native stablecoin) from Aave V3.
Note: GHO is Aave’s native decentralized stablecoin. Unlike other assets, GHO is minted by the protocol rather than supplied by lenders. GHO borrowing uses a specialized VariableDebtToken with unique interest accrual mechanics.
Quick Reference¶
Aspect |
Details |
|---|---|
Entry Point |
|
Token Operations |
|
Key Transformations |
|
State Changes |
|
Events Emitted |
|
GHO Key Differences¶
GHO borrowing differs from regular variable debt borrowing:
Native Minting: GHO is minted by the protocol, not transferred from lenders
Fixed Interest Rate: Unlike variable debt which uses an interest rate strategy, GHO interest rate is set by Aave governance
Virtual Accounting: GHO uses virtual accounting where the underlying is “minted” rather than transferred
Scaled Balances: Uses the standard variable debt scaled balance approach (
amount.rayDiv(index))
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.borrow<br/>GHO_UNDERLYING, amount,<br/>interestRateMode=2,<br/>onBehalfOf"] --> Execute["BorrowLogic<br/>executeBorrow"]
subgraph StateUpdate ["1. State Updates"]
direction TB
UpdateState["ReserveLogic<br/>updateState<br/>Updates indexes"] --> Validate["ValidationLogic<br/>validateBorrow<br/>CRITICAL: Borrow cap"]
class Validate validation
end
subgraph HealthCheck ["2. Health Factor Checks"]
direction TB
CalcAccount["GenericLogic<br/>calculateUserAccountData<br/>Calculates HF, totalDebt"] --> IsolationMode{"Isolation<br/>Mode Active?"}
IsolationMode -->|Yes| IsoDebt["STORAGE UPDATE<br/>isolationModeTotalDebt += amount"]
class IsoDebt storage
IsolationMode -->|No| SkipIso[Continue]
IsoDebt --> IsoEvent["EMIT<br/>IsolationModeTotalDebtUpdated"]
class IsoEvent event
end
subgraph TokenOps ["3. GHO Debt Token Operations"]
direction TB
IsoEvent --> GHOMint["VariableDebtTokenMainnetGHO<br/>mint"]
SkipIso --> GHOMint
GHOMint --> Transform["TRANSFORMATION<br/>scaledAmount =<br/>amount.rayDiv(index)"]
class Transform transformation
Transform --> UpdateUser["TRANSFORMATION<br/>Accrue interest<br/>balanceIncrease =<br/>scaledBalance.rayMul(index) -<br/>scaledBalance.rayMul(previousIndex)"]
class UpdateUser transformation
UpdateUser --> StoreBalance["STORAGE UPDATE<br/>_userState[onBehalfOf]<br/>.balance +=<br/>scaledAmount"]
class StoreBalance storage
StoreBalance --> UpdateIndex["STORAGE UPDATE<br/>_userState[onBehalfOf]<br/>.additionalData = index"]
class UpdateIndex storage
UpdateIndex --> FirstBorrow{"First<br/>Borrow?"}
FirstBorrow -->|Yes| SetBorrow["UserConfig<br/>setBorrowing"]
class SetBorrow storage
FirstBorrow -->|No| SkipFirst[Continue]
SetBorrow --> UpdateRates["ReserveLogic<br/>updateInterestRates"]
SkipFirst --> UpdateRates
end
Execute --> StateUpdate
StateUpdate --> HealthCheck
HealthCheck --> TokenOps
%% Critical path: Mint GHO tokens
UpdateRates --> MintGHO["GHO Facilitator<br/>mintUnderlying<br/>CRITICAL: GHO is<br/>minted, not transferred"]
class MintGHO transformation
MintGHO --> FinalEvent["EMIT<br/>Borrow"]
class FinalEvent event
MintGHO --> MintEvent["EMIT<br/>Mint"]
class MintEvent event
%% Error annotations
%% CRITICAL: Isolation mode debt ceiling enforced
%% CRITICAL: Health factor validated via calculateUserAccountData
%% CRITICAL: Borrow cap prevents protocol overflow
%% CRITICAL: GHO minting requires facilitator authorization
%% Link styles for critical paths
linkStyle 10 stroke:#ff0000,stroke-width:3px
linkStyle 18 stroke:#ff0000,stroke-width:3px
Step-by-Step Execution¶
1. Entry Point¶
File: contracts/protocol/pool/Pool.sol
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode,
uint16 referralCode,
address onBehalfOf
) external virtual override {
BorrowLogic.executeBorrow(
_reserves,
_reservesList,
_eModeCategories,
_usersConfig[onBehalfOf],
DataTypes.ExecuteBorrowParams({
asset: asset, // GHO_UNDERLYING
user: msg.sender,
onBehalfOf: onBehalfOf,
amount: amount,
interestRateMode: DataTypes.InterestRateMode(interestRateMode), // VARIABLE (2)
referralCode: referralCode,
releaseUnderlying: true,
maxStableRateBorrowSizePercent: _maxStableRateBorrowSizePercent,
reservesCount: _reservesCount,
oracle: ADDRESSES_PROVIDER.getPriceOracle(),
userEModeCategory: _usersEModeCategory[onBehalfOf],
priceOracleSentinel: ADDRESSES_PROVIDER.getPriceOracleSentinel()
})
);
}
2. Execute Borrow¶
File: contracts/protocol/libraries/logic/BorrowLogic.sol
function executeBorrow(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteBorrowParams memory params
) external {
DataTypes.ReserveData storage reserve = reserves[params.asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
// Update state
reserve.updateState(reserveCache);
// Validate borrow
ValidationLogic.validateBorrow(
reserves,
reservesList,
eModeCategories,
DataTypes.ValidateBorrowParams({
reserveCache: reserveCache,
userConfig: userConfig,
asset: params.asset,
user: params.user,
amount: params.amount,
interestRateMode: params.interestRateMode,
maxStableLoanPercent: params.maxStableRateBorrowSizePercent,
reservesCount: params.reservesCount,
oracle: params.oracle,
userEModeCategory: params.userEModeCategory
})
);
// Calculate user account data
(
uint256 totalCollateralInBaseCurrency,
uint256 totalDebtInBaseCurrency,
uint256 avgLtv,
uint256 avgLiquidationThreshold,
uint256 healthFactor,
bool hasZeroLtvCollateral
) = GenericLogic.calculateUserAccountData(/* ... */);
// Handle isolation mode
if (hasZeroLtvCollateral) {
reserve.isolationModeTotalDebt += uint128(params.amount);
emit IsolationModeTotalDebtUpdated(
params.asset,
reserve.isolationModeTotalDebt
);
}
// Mint GHO debt tokens (Variable rate only - GHO doesn't support stable)
bool isFirstBorrowing = false;
// For GHO, interestRateMode is VARIABLE (2)
isFirstBorrowing = IVariableDebtToken(reserveCache.variableDebtTokenAddress)
.mint(
params.user,
params.onBehalfOf,
params.amount,
params.amount.rayDiv(reserveCache.nextVariableBorrowIndex), // scaledAmount
reserveCache.nextVariableBorrowIndex
);
// Update user configuration
if (isFirstBorrowing) {
userConfig.setBorrowing(reserve.id, true);
}
// Update interest rates
reserve.updateInterestRates(
reserveCache,
params.asset,
0, // liquidityAdded
params.amount // liquidityTaken (for GHO this triggers mint)
);
// For GHO: underlying is minted via facilitator, not transferred from Pool contract
// The AToken calls the GHO facilitator to mint GHO to the borrower
IAToken(reserveCache.aTokenAddress).transferUnderlyingTo(
params.user,
params.amount
);
emit Borrow(
params.asset,
params.user,
params.onBehalfOf,
params.amount,
params.interestRateMode,
reserveCache.currVariableBorrowRate,
params.referralCode
);
}
3. GHO Variable Debt Token Mint¶
File: contracts/gho/tokenization/VariableDebtTokenMainnetGHO.sol (rev_6)
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 scaledAmount,
uint256 index
) external virtual override onlyPool returns (uint256) {
if (user != onBehalfOf) {
uint256 scaledBalanceOfOnBehalfOf = super.balanceOf(onBehalfOf);
// Handle credit delegation allowance
_decreaseBorrowAllowance(
onBehalfOf,
user,
amount,
(scaledBalanceOfOnBehalfOf + scaledAmount).getVTokenBalance(index) -
scaledBalanceOfOnBehalfOf.getVTokenBalance(index)
);
}
_mintScaled({
caller: user,
onBehalfOf: onBehalfOf,
amountScaled: scaledAmount,
index: index,
getTokenBalance: TokenMath.getVTokenBalance
});
return scaledTotalSupply();
}
4. Scaled Balance Mint Operation¶
File: contracts/protocol/tokenization/base/ScaledBalanceTokenBase.sol
function _mintScaled(
address caller,
address onBehalfOf,
uint256 amountScaled,
uint256 index,
function(uint256, uint256) internal pure returns (uint256) getTokenBalance
) internal returns (bool) {
require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);
uint256 scaledBalance = super.balanceOf(onBehalfOf);
// Calculate interest accrued since last action
uint256 balanceIncrease = scaledBalance.rayMul(index) -
scaledBalance.rayMul(_userState[onBehalfOf].additionalData);
// Update user's last index
_userState[onBehalfOf].additionalData = index.toUint128();
// Mint the scaled tokens
_mint(onBehalfOf, amountScaled.toUint128());
uint256 amountToMint = amount + balanceIncrease;
emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(caller, onBehalfOf, amountToMint, balanceIncrease, index);
return (scaledBalance == 0); // Returns true if this is first borrow
}
5. Balance Calculation (with Discount - Deprecated in v3.4)¶
File: contracts/gho/tokenization/GhoVariableDebtToken.sol (rev_1 - deprecated)
function balanceOf(address user) public view virtual override returns (uint256) {
uint256 scaledBalance = super.balanceOf(user);
if (scaledBalance == 0) {
return 0;
}
uint256 index = POOL.getReserveNormalizedVariableDebt(_underlyingAsset);
uint256 previousIndex = _userState[user].additionalData;
uint256 balance = scaledBalance.rayMul(index);
if (index == previousIndex) {
return balance;
}
// DEPRECATED in v3.4: Discount mechanism
uint256 discountPercent = _ghoUserState[user].discountPercent;
if (discountPercent != 0) {
uint256 balanceIncrease = balance - scaledBalance.rayMul(previousIndex);
balance -= balanceIncrease.percentMul(discountPercent);
}
return balance;
}
6. Validation Checks¶
File: contracts/protocol/libraries/logic/ValidationLogic.sol
function validateBorrow(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.ValidateBorrowParams memory params
) internal view {
require(params.amount != 0, Errors.INVALID_AMOUNT);
// Check reserve is active and borrowing enabled
require(
params.reserveCache.reserveConfiguration.getActive(),
Errors.RESERVE_INACTIVE
);
require(
params.reserveCache.reserveConfiguration.getBorrowingEnabled(),
Errors.BORROWING_NOT_ENABLED
);
require(
!params.reserveCache.reserveConfiguration.getFrozen(),
Errors.RESERVE_FROZEN
);
// Validate oracle
require(
params.oracle != address(0),
Errors.PRICE_ORACLE_SENTINEL_CHECK_FAILED
);
// Get asset price
uint256 assetPrice = IPriceOracleGetter(params.oracle).getAssetPrice(
params.asset
);
require(assetPrice != 0, Errors.PRICE_ORACLE_SENTINEL_CHECK_FAILED);
// Calculate user account data
(
uint256 totalCollateralInBaseCurrency,
uint256 totalDebtInBaseCurrency,
uint256 avgLtv,
,
uint256 healthFactor,
bool hasZeroLtvCollateral
) = GenericLogic.calculateUserAccountData(/* ... */);
// Check borrow cap (applies to GHO too)
uint256 borrowCap = params.reserveCache.reserveConfiguration.getBorrowCap();
if (borrowCap != 0) {
uint256 totalDebt = IERC20(params.reserveCache.variableDebtTokenAddress)
.scaledTotalSupply()
.rayMul(params.reserveCache.nextVariableBorrowIndex);
uint256 scaledCap = borrowCap * 10**params.reserveCache.reserveConfiguration.getDecimals();
require(totalDebt + params.amount <= scaledCap, Errors.BORROW_CAP_EXCEEDED);
}
// Check isolation mode debt ceiling
if (hasZeroLtvCollateral) {
uint256 isolationModeDebtCeiling = params.reserveCache
.reserveConfiguration
.getDebtCeiling();
require(
params.reserveCache.isolationModeTotalDebt + params.amount <=
isolationModeDebtCeiling,
Errors.DEBT_CEILING_EXCEEDED
);
}
// Note: For GHO, there's no available liquidity check since GHO is minted
// The borrow cap check above is the primary constraint
}
Amount Transformations¶
GHO Borrow¶
User requests borrow (WAD decimals)
↓
amount = 1000 * 10^18 // 1000 GHO
↓
nextVariableBorrowIndex = 1.0003 * 10^27 // Current index
↓
scaledAmount = amount.rayDiv(nextVariableBorrowIndex)
= (1000 * 10^18 * 10^27) / (1.0003 * 10^27)
= 999.7 * 10^18 (approximate)
↓
_userState[onBehalfOf].balance += scaledAmount
↓
GHO is minted to user via facilitator
Interest Accrual (View Function)¶
Current scaled balance: 1000 * 10^27 (scaled GHO)
Previous index: 1.05 * 10^27
Current index: 1.10 * 10^27
Calculation:
previousBalance = scaledBalance.rayMul(previousIndex)
= 1000e27 * 1.05e27 / 1e27
= 1050e18 (1050 GHO)
currentBalance = scaledBalance.rayMul(currentIndex)
= 1000e27 * 1.10e27 / 1e27
= 1100e18 (1100 GHO)
balanceIncrease = currentBalance - previousBalance
= 50e18 (50 GHO interest accrued)
Result: User's debt balance is 1100 GHO
Key Differences from Regular Variable Debt¶
No Actual Underlying Transfer: GHO is minted by the facilitator, not transferred from the Pool contract
Fixed Rate via Governance: GHO interest rate is set by Aave governance, not by an algorithmic strategy
Facilitator Model: The AToken for GHO has special logic to mint GHO via the GHO facilitator contract
Virtual Accounting: GHO operates with virtual accounting enabled
Event Details¶
Borrow Event¶
event Borrow(
address indexed reserve, // GHO_UNDERLYING address
address indexed user, // msg.sender
address indexed onBehalfOf, // Debt recipient
uint256 amount, // Amount borrowed (GHO)
DataTypes.InterestRateMode interestRateMode, // VARIABLE (2)
uint256 borrowRate, // Current GHO borrow rate (from governance)
uint16 referralCode // Referral code
);
Mint Event (from VariableDebtToken)¶
event Mint(
address indexed caller, // Address triggering mint (user)
address indexed onBehalfOf, // User receiving debt
uint256 value, // Amount minted (including accrued interest)
uint256 balanceIncrease, // Interest accrued since last action
uint256 index // Current variable debt index
);
Transfer Event¶
event Transfer(
address indexed from, // address(0) for mints
address indexed to, // User receiving debt tokens
uint256 value // Amount transferred (amount + interest)
);
IsolationModeTotalDebtUpdated Event¶
Emitted when borrowing against isolated collateral.
event IsolationModeTotalDebtUpdated(
address indexed asset, // GHO address
uint256 totalDebt // New total isolation mode debt
);
Error Conditions¶
Error |
Condition |
File |
|---|---|---|
|
|
ValidationLogic.sol |
|
GHO reserve is not active |
ValidationLogic.sol |
|
Borrowing is disabled for GHO |
ValidationLogic.sol |
|
GHO reserve is frozen |
ValidationLogic.sol |
|
|
ValidationLogic.sol |
|
|
ValidationLogic.sol |
|
Oracle price is 0 or sentinel check fails |
ValidationLogic.sol |
|
|
ScaledBalanceTokenBase.sol |
Source File Locations¶
contracts/protocol/pool/Pool.sol
contracts/protocol/libraries/logic/BorrowLogic.sol
contracts/protocol/libraries/logic/ValidationLogic.sol
contracts/protocol/libraries/logic/GenericLogic.sol
contracts/gho/tokenization/VariableDebtTokenMainnetGHO.sol
contracts/protocol/tokenization/base/ScaledBalanceTokenBase.sol
contracts/protocol/tokenization/base/DebtTokenBase.sol
contracts/protocol/libraries/logic/ReserveLogic.sol
contracts/protocol/tokenization/AToken.sol
GHO Contract Addresses (Ethereum Mainnet)¶
Contract |
Address |
|---|---|
GHO_UNDERLYING |
|
GHO_A_TOKEN |
|
GHO_V_TOKEN |
|
GHO_ORACLE |
|
GHO_INTEREST_RATE_STRATEGY |
|
Version History¶
Version |
Changes |
|---|---|
rev_1-5 |
Original GhoVariableDebtToken with discount mechanism via stkAAVE |
rev_6+ |
Discount mechanism deprecated (v3.4.0+), now extends VariableDebtToken directly |
DEPRECATED Feature: The GHO discount mechanism (stkAAVE holders receiving interest discounts) was deprecated in Aave V3.4. The
updateDiscountDistributionfunction is now a no-op in current implementations.