Aave V3 代币化机制
1. 概述
Aave V3 使用三种代币来表示用户的存款和债务:
| 代币类型 | 合约 | 用途 | 特点 |
|---|---|---|---|
| aToken | AToken.sol | 存款凭证 | 余额随利息自动增长 |
| StableDebtToken | StableDebtToken.sol | 固定利率债务 | 利率在借款时锁定 |
| VariableDebtToken | VariableDebtToken.sol | 浮动利率债务 | 利率随市场波动 |
2. aToken 详解
2.1 缩放余额机制
aToken 最核心的创新是**缩放余额(Scaled Balance)**机制:
实际余额 (Real Balance) = 缩放余额 (Scaled Balance) × 流动性指数 (Liquidity Index)
为什么使用缩放余额?
传统方式:每次利息累积都更新所有用户余额
问题:Gas 消耗巨大,不可扩展
Aave 方式:存储缩放余额,读取时乘以全局指数
优势:只需更新一个全局变量,所有用户余额自动增长
2.2 余额计算
// ScaledBalanceTokenBase.sol
abstract contract ScaledBalanceTokenBase is MintableIncentivizedERC20, IScaledBalanceToken {
// 获取实际余额
function balanceOf(address user) public view virtual override returns (uint256) {
// 实际余额 = 缩放余额 × 当前流动性指数
return super.balanceOf(user).rayMul(POOL.getReserveNormalizedIncome(_underlyingAsset));
}
// 获取缩放余额(存储的原始值)
function scaledBalanceOf(address user) external view override returns (uint256) {
return super.balanceOf(user);
}
// 获取缩放总供应量
function scaledTotalSupply() public view virtual override returns (uint256) {
return super.totalSupply();
}
// 获取实际总供应量
function totalSupply() public view virtual override returns (uint256) {
uint256 currentSupplyScaled = super.totalSupply();
if (currentSupplyScaled == 0) {
return 0;
}
return currentSupplyScaled.rayMul(POOL.getReserveNormalizedIncome(_underlyingAsset));
}
}2.3 铸造流程
// AToken.sol
function mint(
address caller,
address onBehalfOf,
uint256 amount,
uint256 index
) external virtual override onlyPool returns (bool) {
// 计算缩放金额
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);
// 铸造缩放后的代币
_mint(onBehalfOf, amountScaled.toUint128());
// 发射事件(显示实际金额)
emit Transfer(address(0), onBehalfOf, amount);
emit Mint(caller, onBehalfOf, amount, amountScaled, index);
// 返回是否为首次存款
return scaledBalanceOf(onBehalfOf) == amountScaled;
}铸造示例:
用户存入: 100 USDC
当前流动性指数: 1.05 (1.05e27)
缩放金额 = 100 / 1.05 = 95.24 aUSDC (存储)
实际余额 = 95.24 × 1.05 = 100 aUSDC (显示)
一年后,流动性指数变为 1.10:
实际余额 = 95.24 × 1.10 = 104.76 aUSDC
收益 = 4.76 USDC (自动累积)
2.4 销毁流程
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external virtual override onlyPool {
// 计算缩放金额
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.INVALID_BURN_AMOUNT);
// 销毁 aToken
_burn(from, amountScaled.toUint128());
// 转移底层资产给接收者
if (receiverOfUnderlying != address(this)) {
IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount);
}
emit Transfer(from, address(0), amount);
emit Burn(from, receiverOfUnderlying, amount, amountScaled, index);
}2.5 转账处理
aToken 支持标准 ERC-20 转账:
function _transfer(
address from,
address to,
uint256 amount,
bool validate
) internal {
// 计算缩放金额
uint256 index = POOL.getReserveNormalizedIncome(_underlyingAsset);
uint256 fromBalanceBefore = super.balanceOf(from).rayMul(index);
uint256 toBalanceBefore = super.balanceOf(to).rayMul(index);
// 执行转账
super._transfer(from, to, amount.rayDiv(index).toUint128());
// 更新抵押品状态
if (validate) {
POOL.finalizeTransfer(_underlyingAsset, from, to, amount, fromBalanceBefore, toBalanceBefore);
}
emit BalanceTransfer(from, to, amount.rayDiv(index), index);
}2.6 EIP-712 Permit
aToken 支持无 Gas 授权(签名授权):
bytes32 public constant PERMIT_TYPEHASH =
keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external override {
require(owner != address(0), Errors.ZERO_ADDRESS_NOT_VALID);
require(block.timestamp <= deadline, Errors.INVALID_EXPIRATION);
uint256 currentValidNonce = _nonces[owner];
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR(),
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
)
);
require(owner == ecrecover(digest, v, r, s), Errors.INVALID_SIGNATURE);
_nonces[owner] = currentValidNonce + 1;
_approve(owner, spender, value);
}3. StableDebtToken (固定利率债务)
3.1 特点
- 利率锁定:借款时利率固定,不随市场波动
- 不可转移:禁用 transfer 和 transferFrom
- 独立计息:每个用户有独立的利率和时间戳
3.2 数据结构
contract StableDebtToken is DebtTokenBase, IncentivizedERC20, IStableDebtToken {
// 用户状态:additionalData 存储用户的固定利率
// 继承自 IncentivizedERC20 的 _userState mapping
// 用户借款时间戳
mapping(address => uint40) internal _timestamps;
// 全局平均固定利率
uint128 internal _avgStableRate;
// 固定债务总量
uint128 internal _totalSupply;
}3.3 余额计算
固定利率债务使用复利计算:
function balanceOf(address account) public view virtual override returns (uint256) {
uint256 accountBalance = super.balanceOf(account);
if (accountBalance == 0) {
return 0;
}
// 获取用户的固定利率
uint256 stableRate = _userState[account].additionalData;
// 计算复利
uint256 cumulatedInterest = MathUtils.calculateCompoundedInterest(
stableRate,
_timestamps[account]
);
// 当前债务 = 本金 × 复利因子
return accountBalance.rayMul(cumulatedInterest);
}3.4 铸造(借款)
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 rate
) external override onlyPool returns (bool, uint256, uint256) {
MintLocalVars memory vars;
// 获取用户当前债务
vars.previousBalance = super.balanceOf(onBehalfOf);
vars.currentBalance = _calcUserStableBalance(vars.previousBalance, onBehalfOf);
// 计算新的平均利率
vars.nextStableRate = _calculateNewStableRate(
vars.previousBalance,
_userState[onBehalfOf].additionalData,
amount,
rate
);
// 更新用户利率
_userState[onBehalfOf].additionalData = vars.nextStableRate.toUint128();
// 更新时间戳
_timestamps[onBehalfOf] = uint40(block.timestamp);
// 更新总供应量和全局平均利率
vars.previousSupply = _totalSupply;
vars.currentAvgStableRate = _avgStableRate;
vars.nextSupply = _totalSupply = (vars.previousSupply + amount).toUint128();
vars.nextAvgStableRate = _calculateNextAvgStableRate(
vars.previousSupply,
vars.currentAvgStableRate,
vars.currentBalance + amount,
vars.nextStableRate
);
_avgStableRate = vars.nextAvgStableRate.toUint128();
// 铸造债务代币
_mint(onBehalfOf, (vars.currentBalance + amount).toUint128());
emit Transfer(address(0), onBehalfOf, vars.currentBalance + amount);
emit Mint(
user,
onBehalfOf,
vars.currentBalance + amount,
vars.currentBalance,
vars.nextStableRate,
vars.nextAvgStableRate,
vars.nextSupply
);
return (vars.previousBalance == 0, vars.nextSupply, vars.nextAvgStableRate);
}3.5 平均利率计算
function _calculateNextAvgStableRate(
uint256 previousTotalSupply,
uint256 previousAvgStableRate,
uint256 newAmount,
uint256 newRate
) internal pure returns (uint256) {
// 加权平均: (旧总量 × 旧利率 + 新金额 × 新利率) / 新总量
return (previousTotalSupply.rayMul(previousAvgStableRate) + newAmount.rayMul(newRate))
.rayDiv(previousTotalSupply + newAmount);
}4. VariableDebtToken (浮动利率债务)
4.1 特点
- 利率浮动:利率随市场供需变化
- 缩放机制:与 aToken 类似使用缩放余额
- 不可转移:禁用 transfer 和 transferFrom
4.2 余额计算
function balanceOf(address user) public view virtual override returns (uint256) {
uint256 scaledBalance = super.balanceOf(user);
if (scaledBalance == 0) {
return 0;
}
// 实际债务 = 缩放余额 × 当前可变借贷指数
return scaledBalance.rayMul(POOL.getReserveNormalizedVariableDebt(_underlyingAsset));
}4.3 铸造(借款)
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyPool returns (bool, uint256) {
// 检查金额
if (user != onBehalfOf) {
_decreaseBorrowAllowance(onBehalfOf, user, amount);
}
// 计算缩放金额
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);
// 铸造
uint256 scaledBalance = super.balanceOf(onBehalfOf);
_mint(onBehalfOf, amountScaled.toUint128());
uint256 newScaledBalance = scaledBalance + amountScaled;
emit Transfer(address(0), onBehalfOf, amount);
emit Mint(user, onBehalfOf, amount, amountScaled, index);
return (scaledBalance == 0, newScaledBalance);
}4.4 销毁(还款)
function burn(
address from,
uint256 amount,
uint256 index
) external override onlyPool returns (uint256) {
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.INVALID_BURN_AMOUNT);
_burn(from, amountScaled.toUint128());
uint256 newScaledBalance = super.balanceOf(from);
emit Transfer(from, address(0), amount);
emit Burn(from, amount, amountScaled, index);
return newScaledBalance;
}5. 信用委托机制
5.1 概述
信用委托允许用户将借贷额度授权给第三方,而无需转移抵押品:
Alice (存款人) --授权--> Bob (借款人)
│ │
└──── 信用额度 ─────────┘
5.2 实现
// DebtTokenBase.sol
abstract contract DebtTokenBase {
// 借贷授权额度
mapping(address => mapping(address => uint256)) internal _borrowAllowances;
// 授权借贷额度
function approveDelegation(address delegatee, uint256 amount) external override {
_borrowAllowances[msg.sender][delegatee] = amount;
emit BorrowAllowanceDelegated(msg.sender, delegatee, _getUnderlyingAssetAddress(), amount);
}
// 查询借贷额度
function borrowAllowance(address fromUser, address toUser)
external view override returns (uint256)
{
return _borrowAllowances[fromUser][toUser];
}
// 消耗借贷额度
function _decreaseBorrowAllowance(
address delegator,
address delegatee,
uint256 amount
) internal {
uint256 newAllowance = _borrowAllowances[delegator][delegatee] - amount;
_borrowAllowances[delegator][delegatee] = newAllowance;
emit BorrowAllowanceDelegated(delegator, delegatee, _getUnderlyingAssetAddress(), newAllowance);
}
}5.3 使用场景
场景 1:机构借贷
主账户 A 有大量抵押品
├── 授权子账户 B 借贷 100,000 USDC
├── 授权子账户 C 借贷 50,000 USDC
└── 债务由主账户 A 承担
场景 2:DeFi 协议集成
用户授权 Yield Protocol 使用其借贷额度
├── Protocol 代用户借贷
├── 执行策略操作
└── 利润返还用户
6. 禁用转账
债务代币禁用标准 ERC-20 转账:
// DebtTokenBase.sol
function transfer(address, uint256) external virtual override returns (bool) {
revert(Errors.OPERATION_NOT_SUPPORTED);
}
function allowance(address, address) external view virtual override returns (uint256) {
revert(Errors.OPERATION_NOT_SUPPORTED);
}
function approve(address, uint256) external virtual override returns (bool) {
revert(Errors.OPERATION_NOT_SUPPORTED);
}
function transferFrom(address, address, uint256) external virtual override returns (bool) {
revert(Errors.OPERATION_NOT_SUPPORTED);
}
function increaseAllowance(address, uint256) external virtual override returns (bool) {
revert(Errors.OPERATION_NOT_SUPPORTED);
}
function decreaseAllowance(address, uint256) external virtual override returns (bool) {
revert(Errors.OPERATION_NOT_SUPPORTED);
}为什么禁用?
- 债务是个人责任,不能转移
- 防止逃避清算
- 维护协议安全性
7. 代币对比
7.1 三种代币对比
| 特性 | aToken | StableDebtToken | VariableDebtToken |
|---|---|---|---|
| 用途 | 存款凭证 | 固定利率债务 | 浮动利率债务 |
| 可转移 | ✅ 是 | ❌ 否 | ❌ 否 |
| 余额计算 | 缩放余额 × 指数 | 本金 × 复利 | 缩放余额 × 指数 |
| 利率类型 | 存款利率 | 固定借贷利率 | 浮动借贷利率 |
| 利息累积 | 线性复利 | 复合复利 | 复合复利 |
| Permit | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
7.2 余额计算对比
aToken:
realBalance = scaledBalance × liquidityIndex
StableDebtToken:
realDebt = principal × compoundedInterest(userRate, timeDelta)
VariableDebtToken:
realDebt = scaledBalance × variableBorrowIndex
7.3 Gas 消耗对比
| 操作 | aToken | StableDebtToken | VariableDebtToken |
|---|---|---|---|
| mint | ~50,000 | ~60,000 | ~50,000 |
| burn | ~40,000 | ~50,000 | ~40,000 |
| transfer | ~45,000 | N/A | N/A |
| balanceOf | ~3,000 | ~5,000 | ~3,000 |
8. 激励机制
8.1 IncentivizedERC20
所有代币都继承自 IncentivizedERC20,支持外部激励:
abstract contract IncentivizedERC20 is Context, IERC20Detailed {
// 激励控制器
IRewardsController internal _rewardsController;
// 用户状态
mapping(address => UserState) internal _userState;
struct UserState {
uint128 balance; // 缩放余额
uint128 additionalData; // 附加数据(StableDebtToken 用于存储利率)
}
// 在余额变化时通知激励控制器
function _mint(address account, uint128 amount) internal virtual {
uint256 oldTotalSupply = _totalSupply;
_totalSupply = oldTotalSupply + amount;
uint128 oldAccountBalance = _userState[account].balance;
_userState[account].balance = oldAccountBalance + amount;
IRewardsController rewardsControllerLocal = _rewardsController;
if (address(rewardsControllerLocal) != address(0)) {
rewardsControllerLocal.handleAction(account, oldTotalSupply, oldAccountBalance);
}
}
function _burn(address account, uint128 amount) internal virtual {
uint256 oldTotalSupply = _totalSupply;
_totalSupply = oldTotalSupply - amount;
uint128 oldAccountBalance = _userState[account].balance;
_userState[account].balance = oldAccountBalance - amount;
IRewardsController rewardsControllerLocal = _rewardsController;
if (address(rewardsControllerLocal) != address(0)) {
rewardsControllerLocal.handleAction(account, oldTotalSupply, oldAccountBalance);
}
}
}8.2 激励类型
- 流动性挖矿:持有 aToken 获得额外代币奖励
- 借贷激励:借贷特定资产获得奖励
- 安全模块:质押 stkAAVE 获得安全激励
9. 小结
| 机制 | 实现方式 | 优势 |
|---|---|---|
| 缩放余额 | 存储除以指数的值 | 自动复利,低 Gas |
| 信用委托 | borrowAllowance mapping | 灵活的借贷授权 |
| 禁用转账 | 重写 transfer 函数 | 债务不可逃避 |
| 激励集成 | IncentivizedERC20 | 支持外部激励 |
关键公式:
aToken 余额 = scaledBalance × liquidityIndex
稳定债务 = principal × (1 + rate)^time
可变债务 = scaledBalance × variableBorrowIndex
下一章将讲解 E-Mode、隔离模式、Portal 和闪电贷等高级功能。