# GHO Discount Flow End-to-end execution flow for stkAAVE holders receiving discounts on GHO borrowing interest. > **DEPRECATED**: This feature was deprecated in Aave V3.4. The `updateDiscountDistribution` hook is now a no-op in the current GHO Variable Debt Token implementation. ## Quick Reference | Aspect | Details | |--------|---------| | **Entry Point** | `stkAAVE.transfer()` → `GhoVariableDebtToken.updateDiscountDistribution()` | | **Key Transformations** | `Discount Rate Calculation` | | **State Changes** | `_ghoUserState[user].discountPercent`, `_ghoUserState[user].accumulatedDebtInterest` | | **Events Emitted** | `DiscountPercentUpdated`, `Mint`, `Burn`, `Transfer` | --- ## 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["stkAAVE.transfer
sender, recipient, amount"] --> Hook["GhoVariableDebtToken
updateDiscountDistribution"] subgraph ValidationPhase["1. Validation Phase"] direction TB Hook --> CheckCaller["VALIDATION
msg.sender ==
discountToken"] class CheckCaller validation CheckCaller --> CheckBalances["VALIDATION
senderPreviousScaledBalance > 0
OR
recipientPreviousScaledBalance > 0"] class CheckBalances validation end subgraph AccruePhase["2. Accrue Debt with Discount"] direction TB CheckBalances --> GetIndex["Get index from
POOL.getReserveNormalized
VariableDebt"] GetIndex --> AccrueSender["TRANSFORMATION
_accrueDebtOnAction
sender"] class AccrueSender transformation AccrueSender --> CalculateSender["Calculate:
balanceIncrease =
scaledBalance.rayMul(index) -
scaledBalance.rayMul(previousIndex)"] class CalculateSender transformation CalculateSender --> ApplySender["Apply discount:
discount = balanceIncrease
.percentMul(discountPercent)
discountedBalanceIncrease =
balanceIncrease - discount"] class ApplySender transformation AccrueSender --> BurnSender["STORAGE UPDATE
_burn sender
discountScaled"] class BurnSender storage ApplySender --> AccumulateSender["STORAGE UPDATE
_ghoUserState[sender]
.accumulatedDebtInterest
+= discountedBalanceIncrease"] class AccumulateSender storage end subgraph RefreshPhase["3. Refresh Discount Percent"] direction TB AccumulateSender --> CalcDiscountSender["TRANSFORMATION
calculateDiscountRate
debtBalance, discountTokenBalance"] class CalcDiscountSender transformation CalcDiscountSender --> UpdateSender["STORAGE UPDATE
_ghoUserState[sender]
.discountPercent =
newDiscountPercent"] class UpdateSender storage UpdateSender --> EventSender["EMIT
DiscountPercentUpdated"] class EventSender event end subgraph RecipientPhase["4. Repeat for Recipient"] direction TB EventSender --> AccrueRecipient["TRANSFORMATION
_accrueDebtOnAction
recipient"] class AccrueRecipient transformation AccrueRecipient --> BurnRecipient["STORAGE UPDATE
_burn recipient
discountScaled"] class BurnRecipient storage BurnRecipient --> RefreshRecipient["STORAGE UPDATE
_refreshDiscountPercent
recipient"] class RefreshRecipient storage RefreshRecipient --> EventRecipient["EMIT
DiscountPercentUpdated"] class EventRecipient event end Hook --> ValidationPhase ValidationPhase --> AccruePhase AccruePhase --> RefreshPhase RefreshPhase --> RecipientPhase ``` --- ## Step-by-Step Execution ### 1. Entry Point - stkAAVE Transfer **File:** `contracts/proposals/stkAAVE/stkAAVE.sol` When stkAAVE is transferred, it calls the transfer hook on the GHO Variable Debt Token: ```solidity function _transfer( address sender, address recipient, uint256 amount ) internal override { // ... transfer logic ... // Call the transfer hook (GHO Variable Debt Token) ITransferHook(_transferHook).onTransfer(sender, recipient, amount); // ... emit Transfer event ... } ``` ### 2. Update Discount Distribution **File:** `contracts/gho/tokenization/GhoVariableDebtToken.sol` (rev_1, deprecated in rev_6) ```solidity function updateDiscountDistribution( address sender, address recipient, uint256 senderDiscountTokenBalance, uint256 recipientDiscountTokenBalance, uint256 amount ) external override onlyDiscountToken { uint256 senderPreviousScaledBalance = super.balanceOf(sender); uint256 recipientPreviousScaledBalance = super.balanceOf(recipient); // Skipping computation in case users do not have a position if (senderPreviousScaledBalance == 0 && recipientPreviousScaledBalance == 0) { return; } uint256 index = POOL.getReserveNormalizedVariableDebt(_underlyingAsset); uint256 balanceIncrease; uint256 discountScaled; if (senderPreviousScaledBalance > 0) { (balanceIncrease, discountScaled) = _accrueDebtOnAction( sender, senderPreviousScaledBalance, _ghoUserState[sender].discountPercent, index ); _burn(sender, discountScaled.toUint128()); _refreshDiscountPercent( sender, super.balanceOf(sender).rayMul(index), senderDiscountTokenBalance - amount, _ghoUserState[sender].discountPercent ); emit Transfer(address(0), sender, balanceIncrease); emit Mint(address(0), sender, balanceIncrease, balanceIncrease, index); } if (recipientPreviousScaledBalance > 0) { (balanceIncrease, discountScaled) = _accrueDebtOnAction( recipient, recipientPreviousScaledBalance, _ghoUserState[recipient].discountPercent, index ); _burn(recipient, discountScaled.toUint128()); _refreshDiscountPercent( recipient, super.balanceOf(recipient).rayMul(index), recipientDiscountTokenBalance + amount, _ghoUserState[recipient].discountPercent ); emit Transfer(address(0), recipient, balanceIncrease); emit Mint(address(0), recipient, balanceIncrease, balanceIncrease, index); } } ``` ### 3. Accrue Debt with Discount **File:** `contracts/gho/tokenization/GhoVariableDebtToken.sol` ```solidity function _accrueDebtOnAction( address user, uint256 previousScaledBalance, uint256 discountPercent, uint256 index ) internal returns (uint256, uint256) { uint256 balanceIncrease = previousScaledBalance.rayMul(index) - previousScaledBalance.rayMul(_userState[user].additionalData); uint256 discountScaled = 0; if (balanceIncrease != 0 && discountPercent != 0) { uint256 discount = balanceIncrease.percentMul(discountPercent); discountScaled = discount.rayDiv(index); balanceIncrease = balanceIncrease - discount; } _userState[user].additionalData = index.toUint128(); _ghoUserState[user].accumulatedDebtInterest = (balanceIncrease + _ghoUserState[user].accumulatedDebtInterest).toUint128(); return (balanceIncrease, discountScaled); } ``` ### 4. Refresh Discount Percent **File:** `contracts/gho/tokenization/GhoVariableDebtToken.sol` ```solidity function _refreshDiscountPercent( address user, uint256 balance, uint256 discountTokenBalance, uint256 previousDiscountPercent ) internal { uint256 newDiscountPercent = _discountRateStrategy.calculateDiscountRate( balance, discountTokenBalance ); if (previousDiscountPercent != newDiscountPercent) { _ghoUserState[user].discountPercent = newDiscountPercent.toUint16(); emit DiscountPercentUpdated(user, previousDiscountPercent, newDiscountPercent); } } ``` ### 5. Discount Rate Calculation **File:** `contracts/gho/interestStrategy/GhoDiscountRateStrategy.sol` ```solidity function calculateDiscountRate( uint256 debtBalance, uint256 discountTokenBalance ) external pure override returns (uint256) { if (discountTokenBalance < MIN_DISCOUNT_TOKEN_BALANCE || debtBalance < MIN_DEBT_TOKEN_BALANCE) { return 0; } else { uint256 discountedBalance = discountTokenBalance.wadMul(GHO_DISCOUNTED_PER_DISCOUNT_TOKEN); if (discountedBalance >= debtBalance) { return DISCOUNT_RATE; // 30% (3000 bps) } else { return (discountedBalance * DISCOUNT_RATE) / debtBalance; } } } ``` --- ## Amount Transformations ### Discount Rate Calculation ``` User has: - Debt Balance: 1000 GHO (1000 * 10^18) - stkAAVE Balance: 5 tokens (5 * 10^18) Constants: - GHO_DISCOUNTED_PER_DISCOUNT_TOKEN = 100 * 10^18 - DISCOUNT_RATE = 3000 (30% in bps) - MIN_DISCOUNT_TOKEN_BALANCE = 0.001 * 10^18 - MIN_DEBT_TOKEN_BALANCE = 1 * 10^18 Calculation: Step 1: Check minimums discountTokenBalance (5e18) >= MIN_DISCOUNT_TOKEN_BALANCE (1e15) ✓ debtBalance (1000e18) >= MIN_DEBT_TOKEN_BALANCE (1e18) ✓ Step 2: Calculate discounted balance discountedBalance = discountTokenBalance.wadMul(GHO_DISCOUNTED_PER_DISCOUNT_TOKEN) = 5e18 * 100e18 / 1e18 = 500e18 (500 GHO worth) Step 3: Compare to debt discountedBalance (500e18) < debtBalance (1000e18) → Proportional discount Step 4: Calculate discount rate discountRate = (discountedBalance * DISCOUNT_RATE) / debtBalance = (500e18 * 3000) / 1000e18 = 1500 (15% in bps) Result: User gets 15% discount on interest accrual ``` ### Interest Accrual with Discount ``` User has: - Scaled Balance: 100e27 (100 scaled GHO) - Previous Index: 1.05e27 - Current Index: 1.10e27 - Discount Percent: 1500 (15%) Calculation: Step 1: Calculate gross balance increase previousBalance = scaledBalance.rayMul(previousIndex) = 100e27 * 1.05e27 / 1e27 = 105e18 (105 GHO) currentBalance = scaledBalance.rayMul(currentIndex) = 100e27 * 1.10e27 / 1e27 = 110e18 (110 GHO) balanceIncrease = currentBalance - previousBalance = 110e18 - 105e18 = 5e18 (5 GHO) Step 2: Apply discount discount = balanceIncrease.percentMul(discountPercent) = 5e18 * 1500 / 10000 = 0.75e18 (0.75 GHO) discountedBalanceIncrease = balanceIncrease - discount = 5e18 - 0.75e18 = 4.25e18 (4.25 GHO) Step 3: Convert discount to scaled amount discountScaled = discount.rayDiv(index) = 0.75e18 * 1e27 / 1.10e27 ≈ 0.6818e18 Step 4: Update state _burn(user, discountScaled) // Burn discount from debt accumulatedDebtInterest += discountedBalanceIncrease Result: User only pays interest on 4.25 GHO instead of 5 GHO ``` --- ## Event Details ### DiscountPercentUpdated Emitted when a user's discount percent changes due to stkAAVE balance or GHO debt changes. ```solidity event DiscountPercentUpdated( address indexed user, // User address uint256 oldDiscountPercent, // Previous discount (bps) uint256 indexed newDiscountPercent // New discount (bps) ); ``` ### Mint Emitted when interest accrues (after discount is applied). ```solidity event Mint( address indexed caller, // Address triggering mint (address(0) for interest) address indexed onBehalfOf, // User receiving minted debt uint256 value, // Amount minted (discounted interest) uint256 balanceIncrease, // Interest accrued uint256 index // Current variable debt index ); ``` ### Burn Emitted when discount is applied (burning the discounted portion). ```solidity event Burn( address indexed from, // User whose debt is burned address indexed target, // Target address (usually address(0)) uint256 value, // Amount burned (discountScaled) uint256 balanceIncrease, // Total interest before discount uint256 index // Current variable debt index ); ``` ### Transfer Standard ERC20 transfer event for interest accrual. ```solidity event Transfer( address indexed from, // address(0) for mints address indexed to, // User receiving interest debt uint256 value // Amount transferred ); ``` --- ## Error Conditions | Error | Condition | File | |-------|-----------|------| | `CALLER_NOT_DISCOUNT_TOKEN` | `msg.sender != _discountToken` | GhoVariableDebtToken.sol | | `ATOKEN_ALREADY_SET` | Attempting to set AToken when already initialized | GhoVariableDebtToken.sol | | `ZERO_ADDRESS_NOT_VALID` | Setting zero address for AToken or strategy | GhoVariableDebtToken.sol | --- ## Related Flows - [Borrow Flow](./borrow.md) - GHO borrowing creates debt subject to discounts - [Repay Flow](./repay.md) - Debt repayment that triggers discount recalculation - [Liquidation Flow](./liquidation.md) - Liquidation that affects GHO debt --- ## Source File Locations ``` contracts/gho/tokenization/GhoVariableDebtToken.sol contracts/gho/interestStrategy/GhoDiscountRateStrategy.sol contracts/gho/interestStrategy/interfaces/IGhoDiscountRateStrategy.sol contracts/gho/tokenization/interfaces/IGhoVariableDebtToken.sol contracts/proposals/stkAAVE/stkAAVE.sol ``` --- ## Constants Reference | Constant | Value | Description | |----------|-------|-------------| | `GHO_DISCOUNTED_PER_DISCOUNT_TOKEN` | 100e18 | GHO debt entitled to discount per 1 stkAAVE | | `DISCOUNT_RATE` | 3000 (30%) | Maximum discount percentage in bps | | `MIN_DISCOUNT_TOKEN_BALANCE` | 1e15 (0.001) | Minimum stkAAVE to qualify | | `MIN_DEBT_TOKEN_BALANCE` | 1e18 (1 GHO) | Minimum GHO debt to qualify | --- ## Deprecation Notice > **DEPRECATED in V3.4**: The GHO discount mechanism was deprecated in Aave V3.4. The current implementation of `updateDiscountDistribution` in `GhoVariableDebtToken` (rev_6+) is a no-op: > > ```solidity > // @note deprecated discount hook being called by stkAAVE, not used since v3.4 > function updateDiscountDistribution(address, address, uint256, uint256, uint256) external {} > ``` > > The `_ghoUserState` mapping storage slot is marked as deprecated and should not be reused to avoid storage layout conflicts.