npx skills add https://github.com/cyotee/aave-v4-skill --skill 'Aave V4 Dynamic Config'Aave V4 引入了动态风险配置功能,允许多个风险参数集共存。仓位绑定到特定的配置,治理层可以更新参数,而不会立即影响现有用户。
┌──────────────────────────────────────────────────────────────┐
│ DYNAMIC RISK CONFIGURATION │
├──────────────────────────────────────────────────────────────┤
│ │
│ V3 Problem: │
│ • Single global config per asset │
│ • Any change affects ALL positions immediately │
│ • Lowering LT can trigger unexpected liquidations │
│ │
│ V4 Solution: │
│ • Multiple configs exist side-by-side │
│ • Positions bind to specific configKey │
│ • Old configs govern old positions │
│ • New configs apply to new/updated positions │
│ │
└──────────────────────────────────────────────────────────────┘
// Each reserve tracks current and historical configs
struct Reserve {
// ...
uint24 dynamicConfigKey; // Current active config key
}
// Mapping of configs per reserve
mapping(uint256 reserveId => mapping(uint24 configKey => DynamicReserveConfig)) internal _dynamicConfig;
// User positions store their bound config key
struct UserPosition {
// ...
uint24 dynamicConfigKey; // Position's config snapshot
}
struct DynamicReserveConfig {
uint16 collateralFactor; // CF: max borrow power (BPS, e.g., 82_50 = 82.5%)
uint16 maxLiquidationBonus; // Max LB (e.g., 105_00 = 5% bonus)
uint16 liquidationFee; // Protocol fee on liquidation (BPS)
}
┌──────────────────────────────────────────────────────────────┐
│ CONFIG KEYS │
├──────────────────────────────────────────────────────────────┤
│ │
│ Time → │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ Key 0 │ Key 1 │ Key 2 │ Key 3 │ ← Reserve keys │
│ │ CF=85% │ CF=82% │ CF=80% │ CF=78% │ │
│ └────┬────┴────┬────┴────┬────┴────┬────┘ │
│ │ │ │ │ │
│ User A binds │ User B binds │ │
│ to Key 0 │ to Key 2 │ │
│ │ │ │
│ User A still │ User C binds │
│ uses CF=85% │ to Key 3 │
│ │
│ Max keys: 2^24 = 16M │
│ │
└──────────────────────────────────────────────────────────────┘
/// @notice Add a new dynamic config (increments key)
function addDynamicReserveConfig(
uint256 reserveId,
DynamicReserveConfig calldata config
) external restricted returns (uint24 newKey) {
Reserve storage reserve = _getReserve(reserveId);
_validateDynamicReserveConfig(config);
newKey = reserve.dynamicConfigKey + 1;
require(newKey <= MAX_ALLOWED_DYNAMIC_CONFIG_KEY, MaxConfigKeysReached());
reserve.dynamicConfigKey = newKey;
_dynamicConfig[reserveId][newKey] = config;
emit AddDynamicReserveConfig(reserveId, newKey, config);
}
/// @notice Update an existing config (affects bound positions)
function updateDynamicReserveConfig(
uint256 reserveId,
uint24 configKey,
DynamicReserveConfig calldata config
) external restricted {
require(configKey <= _reserves[reserveId].dynamicConfigKey, InvalidConfigKey());
_validateDynamicReserveConfig(config);
_dynamicConfig[reserveId][configKey] = config;
emit UpdateDynamicReserveConfig(reserveId, configKey, config);
}
仓位的 configKey 快照在 增加风险的操作 时更新:
| 操作 | 更新配置键? | 原因 |
|---|---|---|
borrow | ✅ 是 | 增加债务 = 增加风险 |
withdraw | ✅ 是 | 减少抵押品 = 增加风险 |
disableUsingAsCollateral | ✅ 是 | 减少抵押能力 |
enableUsingAsCollateral | ✅ 部分 | 仅针对该储备资产 |
supply | ❌ 否 | 降低风险 |
function _refreshUserDynamicConfig(address user) internal {
PositionStatus storage status = _positionStatus[user];
// Check if position can use latest config
uint256 collateralBitmap = status.usingAsCollateralBitmap;
while (collateralBitmap != 0) {
uint256 reserveId = _getNextReserveId(collateralBitmap);
collateralBitmap = _clearBit(collateralBitmap, reserveId);
uint24 latestKey = _reserves[reserveId].dynamicConfigKey;
UserPosition storage position = _userPositions[user][reserveId];
// Rebind to latest key
position.dynamicConfigKey = latestKey;
}
}
如果最新配置会使仓位变得不健康,则操作会回滚:
function _validateAndRebind(address user) internal {
// Temporarily bind to latest config
_refreshUserDynamicConfig(user);
// Check health with latest config
uint256 hf = getUserHealthFactor(user);
if (hf < HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
// Latest config would make position unhealthy
revert UnhealthyPosition();
}
// Position is healthy with latest config - keep it bound
}
用户可以自愿更新其配置:
/// @notice Update user's config keys to latest
function updateUserDynamicConfig() external {
_refreshUserDynamicConfig(msg.sender);
// Validate health factor
require(
getUserHealthFactor(msg.sender) >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
UnhealthyPosition()
);
emit UpdateUserDynamicConfig(msg.sender);
}
// Scenario: Governance wants to lower CF from 85% to 80%
// Step 1: Add new config with lower CF
uint24 newKey = spoke.addDynamicReserveConfig(
reserveId,
DynamicReserveConfig({
collateralFactor: 80_00, // 80% (was 85%)
maxLiquidationBonus: 105_00,
liquidationFee: 10_00
})
);
// Step 2: Existing positions keep using old CF
// Step 3: New positions use new CF
// Step 4: Existing users rebind on their next risk-increasing action
在特殊情况下,治理层可以强制更新特定仓位:
/// @notice Force update a user's config to latest
function forceUpdateUserDynamicConfig(address user) external restricted {
_refreshUserDynamicConfig(user);
// Note: This might make the position liquidatable
// Use only when necessary for protocol safety
}
治理层可以修改历史配置(影响绑定的仓位):
// Scenario: Security issue found, need to lower CF on all configs
for (uint24 key = 0; key <= latestKey; key++) {
spoke.updateDynamicReserveConfig(
reserveId,
key,
DynamicReserveConfig({
collateralFactor: 70_00, // Emergency reduction
maxLiquidationBonus: 110_00,
liquidationFee: 10_00
})
);
}
健康因子使用仓位绑定的 configKey:
function getUserHealthFactor(address user) public view returns (uint256) {
uint256 totalCollateralValue = 0;
uint256 totalDebtValue = 0;
// For each collateral reserve
uint256 collateralBitmap = _positionStatus[user].usingAsCollateralBitmap;
while (collateralBitmap != 0) {
uint256 reserveId = _getNextReserveId(collateralBitmap);
collateralBitmap = _clearBit(collateralBitmap, reserveId);
UserPosition storage position = _userPositions[user][reserveId];
// Use position's bound config, not latest
DynamicReserveConfig storage config = _dynamicConfig[reserveId][position.dynamicConfigKey];
uint256 supplyValue = _getPositionSupplyValue(user, reserveId);
uint256 adjustedValue = supplyValue.percentMul(config.collateralFactor);
totalCollateralValue += adjustedValue;
}
// ... calculate debt and return HF
}
/// @notice Get current active config key for reserve
function getReserveDynamicConfigKey(uint256 reserveId) external view returns (uint24) {
return _reserves[reserveId].dynamicConfigKey;
}
/// @notice Get config for a specific key
function getDynamicReserveConfig(
uint256 reserveId,
uint24 configKey
) external view returns (DynamicReserveConfig memory) {
return _dynamicConfig[reserveId][configKey];
}
/// @notice Get user's bound config key for a reserve
function getUserDynamicConfigKey(
address user,
uint256 reserveId
) external view returns (uint24) {
return _userPositions[user][reserveId].dynamicConfigKey;
}
event AddDynamicReserveConfig(
uint256 indexed reserveId,
uint24 indexed configKey,
DynamicReserveConfig config
);
event UpdateDynamicReserveConfig(
uint256 indexed reserveId,
uint24 indexed configKey,
DynamicReserveConfig config
);
event UpdateUserDynamicConfig(address indexed user);
┌──────────────────────────────────────────────────────────────┐
│ DYNAMIC CONFIG BENEFITS │
├──────────────────────────────────────────────────────────────┤
│ │
│ For Governance: │
│ • Adjust parameters without harming existing users │
│ • Gradual migration to new risk settings │
│ • Emergency changes when needed (via update) │
│ │
│ For Users: │
│ • No surprise liquidations from parameter changes │
│ • Old positions maintain their terms │
│ • Voluntary upgrade to new configs │
│ │
│ For Protocol: │
│ • More agile risk management │
│ • Better user experience │
│ • Reduced governance friction │
│ │
└──────────────────────────────────────────────────────────────┘
src/spoke/Spoke.sol - 动态配置实现src/spoke/interfaces/ISpoke.sol - DynamicReserveConfig 结构体docs/overview.md - 动态配置设计每周安装次数
–
代码仓库
首次出现
–
安全审计
Aave V4 introduces Dynamic Risk Configuration, allowing multiple risk parameter sets to coexist. Positions bind to specific configurations, and governance can update parameters without immediately affecting existing users.
┌──────────────────────────────────────────────────────────────┐
│ DYNAMIC RISK CONFIGURATION │
├──────────────────────────────────────────────────────────────┤
│ │
│ V3 Problem: │
│ • Single global config per asset │
│ • Any change affects ALL positions immediately │
│ • Lowering LT can trigger unexpected liquidations │
│ │
│ V4 Solution: │
│ • Multiple configs exist side-by-side │
│ • Positions bind to specific configKey │
│ • Old configs govern old positions │
│ • New configs apply to new/updated positions │
│ │
└──────────────────────────────────────────────────────────────┘
// Each reserve tracks current and historical configs
struct Reserve {
// ...
uint24 dynamicConfigKey; // Current active config key
}
// Mapping of configs per reserve
mapping(uint256 reserveId => mapping(uint24 configKey => DynamicReserveConfig)) internal _dynamicConfig;
// User positions store their bound config key
struct UserPosition {
// ...
uint24 dynamicConfigKey; // Position's config snapshot
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
repay |
| ❌ 否 |
| 降低风险 |
liquidationCall | ❌ 否 | 改善健康状况 |
updateRiskPremium | ❌ 否 | 无风险变化 |
struct DynamicReserveConfig {
uint16 collateralFactor; // CF: max borrow power (BPS, e.g., 82_50 = 82.5%)
uint16 maxLiquidationBonus; // Max LB (e.g., 105_00 = 5% bonus)
uint16 liquidationFee; // Protocol fee on liquidation (BPS)
}
┌──────────────────────────────────────────────────────────────┐
│ CONFIG KEYS │
├──────────────────────────────────────────────────────────────┤
│ │
│ Time → │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ Key 0 │ Key 1 │ Key 2 │ Key 3 │ ← Reserve keys │
│ │ CF=85% │ CF=82% │ CF=80% │ CF=78% │ │
│ └────┬────┴────┬────┴────┬────┴────┬────┘ │
│ │ │ │ │ │
│ User A binds │ User B binds │ │
│ to Key 0 │ to Key 2 │ │
│ │ │ │
│ User A still │ User C binds │
│ uses CF=85% │ to Key 3 │
│ │
│ Max keys: 2^24 = 16M │
│ │
└──────────────────────────────────────────────────────────────┘
/// @notice Add a new dynamic config (increments key)
function addDynamicReserveConfig(
uint256 reserveId,
DynamicReserveConfig calldata config
) external restricted returns (uint24 newKey) {
Reserve storage reserve = _getReserve(reserveId);
_validateDynamicReserveConfig(config);
newKey = reserve.dynamicConfigKey + 1;
require(newKey <= MAX_ALLOWED_DYNAMIC_CONFIG_KEY, MaxConfigKeysReached());
reserve.dynamicConfigKey = newKey;
_dynamicConfig[reserveId][newKey] = config;
emit AddDynamicReserveConfig(reserveId, newKey, config);
}
/// @notice Update an existing config (affects bound positions)
function updateDynamicReserveConfig(
uint256 reserveId,
uint24 configKey,
DynamicReserveConfig calldata config
) external restricted {
require(configKey <= _reserves[reserveId].dynamicConfigKey, InvalidConfigKey());
_validateDynamicReserveConfig(config);
_dynamicConfig[reserveId][configKey] = config;
emit UpdateDynamicReserveConfig(reserveId, configKey, config);
}
The position's configKey snapshot updates on risk-increasing actions :
| Action | Updates Config Key? | Reason |
|---|---|---|
borrow | ✅ Yes | Increases debt = increases risk |
withdraw | ✅ Yes | Reduces collateral = increases risk |
disableUsingAsCollateral | ✅ Yes | Reduces collateral power |
enableUsingAsCollateral | ✅ Partial | Only for that reserve |
supply | ❌ No | Decreases risk |
repay | ❌ No | Decreases risk |
liquidationCall | ❌ No | Improves health |
updateRiskPremium | ❌ No | No risk change |
function _refreshUserDynamicConfig(address user) internal {
PositionStatus storage status = _positionStatus[user];
// Check if position can use latest config
uint256 collateralBitmap = status.usingAsCollateralBitmap;
while (collateralBitmap != 0) {
uint256 reserveId = _getNextReserveId(collateralBitmap);
collateralBitmap = _clearBit(collateralBitmap, reserveId);
uint24 latestKey = _reserves[reserveId].dynamicConfigKey;
UserPosition storage position = _userPositions[user][reserveId];
// Rebind to latest key
position.dynamicConfigKey = latestKey;
}
}
If latest config would make position unhealthy, the action reverts:
function _validateAndRebind(address user) internal {
// Temporarily bind to latest config
_refreshUserDynamicConfig(user);
// Check health with latest config
uint256 hf = getUserHealthFactor(user);
if (hf < HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
// Latest config would make position unhealthy
revert UnhealthyPosition();
}
// Position is healthy with latest config - keep it bound
}
Users can voluntarily update their config:
/// @notice Update user's config keys to latest
function updateUserDynamicConfig() external {
_refreshUserDynamicConfig(msg.sender);
// Validate health factor
require(
getUserHealthFactor(msg.sender) >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
UnhealthyPosition()
);
emit UpdateUserDynamicConfig(msg.sender);
}
// Scenario: Governance wants to lower CF from 85% to 80%
// Step 1: Add new config with lower CF
uint24 newKey = spoke.addDynamicReserveConfig(
reserveId,
DynamicReserveConfig({
collateralFactor: 80_00, // 80% (was 85%)
maxLiquidationBonus: 105_00,
liquidationFee: 10_00
})
);
// Step 2: Existing positions keep using old CF
// Step 3: New positions use new CF
// Step 4: Existing users rebind on their next risk-increasing action
In exceptional cases, governance can force-update specific positions:
/// @notice Force update a user's config to latest
function forceUpdateUserDynamicConfig(address user) external restricted {
_refreshUserDynamicConfig(user);
// Note: This might make the position liquidatable
// Use only when necessary for protocol safety
}
Governance can modify historical configs (affects bound positions):
// Scenario: Security issue found, need to lower CF on all configs
for (uint24 key = 0; key <= latestKey; key++) {
spoke.updateDynamicReserveConfig(
reserveId,
key,
DynamicReserveConfig({
collateralFactor: 70_00, // Emergency reduction
maxLiquidationBonus: 110_00,
liquidationFee: 10_00
})
);
}
Health factor uses the position's bound configKey:
function getUserHealthFactor(address user) public view returns (uint256) {
uint256 totalCollateralValue = 0;
uint256 totalDebtValue = 0;
// For each collateral reserve
uint256 collateralBitmap = _positionStatus[user].usingAsCollateralBitmap;
while (collateralBitmap != 0) {
uint256 reserveId = _getNextReserveId(collateralBitmap);
collateralBitmap = _clearBit(collateralBitmap, reserveId);
UserPosition storage position = _userPositions[user][reserveId];
// Use position's bound config, not latest
DynamicReserveConfig storage config = _dynamicConfig[reserveId][position.dynamicConfigKey];
uint256 supplyValue = _getPositionSupplyValue(user, reserveId);
uint256 adjustedValue = supplyValue.percentMul(config.collateralFactor);
totalCollateralValue += adjustedValue;
}
// ... calculate debt and return HF
}
/// @notice Get current active config key for reserve
function getReserveDynamicConfigKey(uint256 reserveId) external view returns (uint24) {
return _reserves[reserveId].dynamicConfigKey;
}
/// @notice Get config for a specific key
function getDynamicReserveConfig(
uint256 reserveId,
uint24 configKey
) external view returns (DynamicReserveConfig memory) {
return _dynamicConfig[reserveId][configKey];
}
/// @notice Get user's bound config key for a reserve
function getUserDynamicConfigKey(
address user,
uint256 reserveId
) external view returns (uint24) {
return _userPositions[user][reserveId].dynamicConfigKey;
}
event AddDynamicReserveConfig(
uint256 indexed reserveId,
uint24 indexed configKey,
DynamicReserveConfig config
);
event UpdateDynamicReserveConfig(
uint256 indexed reserveId,
uint24 indexed configKey,
DynamicReserveConfig config
);
event UpdateUserDynamicConfig(address indexed user);
┌──────────────────────────────────────────────────────────────┐
│ DYNAMIC CONFIG BENEFITS │
├──────────────────────────────────────────────────────────────┤
│ │
│ For Governance: │
│ • Adjust parameters without harming existing users │
│ • Gradual migration to new risk settings │
│ • Emergency changes when needed (via update) │
│ │
│ For Users: │
│ • No surprise liquidations from parameter changes │
│ • Old positions maintain their terms │
│ • Voluntary upgrade to new configs │
│ │
│ For Protocol: │
│ • More agile risk management │
│ • Better user experience │
│ • Reduced governance friction │
│ │
└──────────────────────────────────────────────────────────────┘
src/spoke/Spoke.sol - Dynamic config implementationsrc/spoke/interfaces/ISpoke.sol - DynamicReserveConfig structdocs/overview.md - Dynamic configuration designWeekly Installs
–
Repository
First Seen
–
Security Audits
AI代理钱包工具 - 多链资产管理、余额查询、转账签名与交易历史
3,800 周安装