Skip to content

Commit

Permalink
Merge pull request #4 from bgd-labs/feat/capo-update
Browse files Browse the repository at this point in the history
Feat: capo updates
  • Loading branch information
eboadom authored May 21, 2024
2 parents 204041f + 493d7d0 commit 7fbb7b5
Show file tree
Hide file tree
Showing 9 changed files with 736 additions and 37 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/protocol-v3.1-upgrade"]
path = lib/protocol-v3.1-upgrade
url = https://github.com/bgd-labs/protocol-v3.1-upgrade
[submodule "lib/aave-capo"]
path = lib/aave-capo
url = https://github.com/bgd-labs/aave-capo
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ The following risk params could be changed by the RiskStewards:
- Slope 2
- Optimal point

- Cap parameters for [PriceCapAdapters (CAPO)](https://github.com/bgd-labs/aave-capo/)

#### Min Delay:

For each risk param, `minDelay` can be configured, which is the minimum amount of delay (denominated in seconds) required before pushing another update for the risk param. Please note that this is specific for a risk param and includes both in upwards and downwards direction. Ex. after increasing LTV by 5%, we must wait by `minDelay` before either increasing it again or decreasing it.
Expand All @@ -46,13 +48,19 @@ For each risk param, `maxPercentChange` which is the maximum percent change allo
- Interest rates params: For Base Variable Borrow Rate, Slope 1, Slope 2, uOptimal the `maxPercentChange` is in absolute values and is denominated in BPS.
For example, for a current uOptimal of an asset configured at 50_00 (50%) and `maxPercentChange` configured for uOptimal at `10_00`, the max ltv that can be configured is 55_00 (55%) and the minimum 45_00 (45%) via the steward.

- LST Cap adapter params: `snapshotRatio` must be less or equal to the actual one. The `maxPercentChange` is applied to `maxYearlyGrowthPercent`, it is relative and is denominated in BPS. (Ex. `10_00` for +-10% relative change).
For example, for a max yearly growth percent at 10_00 and `maxPercentChange` configured at `10_00`, the max yearly growth percent that can be configured is 11_00 and the minimum 9_00 via the steward.

- Stable price cap: the `maxPercentChange` is in relative values.
For example, for a current price cap of an oracle configured at 1_10_000000 and `maxPercentChange` configured at `1_00`, the max price cap that can be configured is 1_11_100000 and the minimum 1_08_900000 via the steward.

After the activation proposal, these params could only be changed by the governance by calling the `setRiskConfig()` method.

_Note: The Risk Stewards will not allow setting the values to 0 for supply cap, borrow cap, debt ceiling, LTV, Liquidation Threshold, Liquidation Bonus no matter if the maxPercentChange has been configured to 100%. The Risk Stewards will however allow setting the value to 0 for interest rate param updates._

#### Restricted Assets:
#### Restricted Assets and Oracles:

Some assets can also be restricted on the RiskStewards by calling the `setAssetRestricted()` method. This prevents the RiskStewards to make any updates on the specific asset. One example of the restricted asset could be GHO.
Some assets/oracles can also be restricted on the RiskStewards by calling the `setAddressRestricted()` method. This prevents the RiskStewards to make any updates on the specific asset. One example of the restricted asset could be GHO.

### Setup

Expand Down
Binary file modified docs/risk-steward-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/aave-capo
Submodule aave-capo added at f7a31a
10 changes: 10 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@ lib/aave-v3-origin:aave-v3-core/=lib/aave-v3-origin/src/core
lib/aave-v3-origin:aave-v3-periphery/=lib/aave-v3-origin/src/periphery/
lib/protocol-v3.1-upgrade/lib/aave-helpers/lib/aave-address-book:aave-v3-core/=lib/protocol-v3.1-upgrade/lib/aave-helpers/lib/aave-address-book/lib/aave-v3-core/
lib/aave-helpers:aave-v3-core/=lib/aave-helpers/lib/aave-address-book/lib/aave-v3-core/
lib/aave-helpers/lib/aave-address-book:aave-v3-core/=lib/protocol-v3.1-upgrade/lib/aave-helpers/lib/aave-address-book/lib/aave-v3-core/
aave-capo/=lib/aave-capo/src
lib/aave-capo:cl-synchronicity-price-adapter/=lib/aave-capo/lib/cl-synchronicity-price-adapter/src/

@aave/core-v3/=lib/protocol-v3.1-upgrade/lib/aave-helpers/lib/aave-address-book/lib/aave-v3-core/
@aave/periphery-v3/=lib/protocol-v3.1-upgrade/lib/aave-helpers/lib/aave-address-book/lib/aave-v3-periphery/
aave-v3-core/=lib/protocol-v3.1-upgrade/lib/aave-v3-origin/src/core/
aave-v3-periphery/=lib/protocol-v3.1-upgrade/lib/aave-v3-origin/src/periphery/
governance-crosschain-bridges/=lib/protocol-v3.1-upgrade/lib/aave-helpers/lib/governance-crosschain-bridges/
protocol-v3.1-upgrade/=lib/protocol-v3.1-upgrade/
138 changes: 129 additions & 9 deletions src/contracts/RiskSteward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ pragma solidity ^0.8.0;

import {IPoolDataProvider} from 'aave-address-book/AaveV3.sol';
import {Address} from 'solidity-utils/contracts/oz-common/Address.sol';
import {SafeCast} from 'solidity-utils/contracts/oz-common/SafeCast.sol';
import {EngineFlags} from 'aave-helpers/v3-config-engine/EngineFlags.sol';
import {Ownable} from 'solidity-utils/contracts/oz-common/Ownable.sol';
import {IAaveV3ConfigEngine as IEngine} from 'aave-v3-origin/periphery/contracts/v3-config-engine/AaveV3ConfigEngine.sol';
import {IRiskSteward} from '../interfaces/IRiskSteward.sol';
import {IDefaultInterestRateStrategyV2} from 'aave-v3-origin/core/contracts/interfaces/IDefaultInterestRateStrategyV2.sol';
import {IPriceCapAdapter} from 'aave-capo/interfaces/IPriceCapAdapter.sol';
import {IPriceCapAdapterStable} from 'aave-capo/interfaces/IPriceCapAdapterStable.sol';

/**
* @title RiskSteward
Expand All @@ -17,6 +20,8 @@ import {IDefaultInterestRateStrategyV2} from 'aave-v3-origin/core/contracts/inte
*/
contract RiskSteward is Ownable, IRiskSteward {
using Address for address;
using SafeCast for uint256;
using SafeCast for int256;

/// @inheritdoc IRiskSteward
IEngine public immutable CONFIG_ENGINE;
Expand All @@ -33,7 +38,7 @@ contract RiskSteward is Ownable, IRiskSteward {

mapping(address => Debounce) internal _timelocks;

mapping(address => bool) internal _restrictedAssets;
mapping(address => bool) internal _restrictedAddresses;

/**
* @dev Modifier preventing anyone, but the council to update risk params.
Expand Down Expand Up @@ -81,6 +86,22 @@ contract RiskSteward is Ownable, IRiskSteward {
_updateCollateralSide(collateralUpdates);
}

/// @inheritdoc IRiskSteward
function updateLstPriceCaps(
PriceCapLstUpdate[] calldata priceCapUpdates
) external onlyRiskCouncil {
_validatePriceCapUpdate(priceCapUpdates);
_updateLstPriceCaps(priceCapUpdates);
}

/// @inheritdoc IRiskSteward
function updateStablePriceCaps(
PriceCapStableUpdate[] calldata priceCapUpdates
) external onlyRiskCouncil {
_validatePriceCapStableUpdate(priceCapUpdates);
_updateStablePriceCaps(priceCapUpdates);
}

/// @inheritdoc IRiskSteward
function getTimelock(address asset) external view returns (Debounce memory) {
return _timelocks[asset];
Expand All @@ -98,14 +119,14 @@ contract RiskSteward is Ownable, IRiskSteward {
}

/// @inheritdoc IRiskSteward
function isAssetRestricted(address asset) external view returns (bool) {
return _restrictedAssets[asset];
function isAddressRestricted(address contractAddress) external view returns (bool) {
return _restrictedAddresses[contractAddress];
}

/// @inheritdoc IRiskSteward
function setAssetRestricted(address asset, bool isRestricted) external onlyOwner {
_restrictedAssets[asset] = isRestricted;
emit AssetRestricted(asset, isRestricted);
function setAddressRestricted(address contractAddress, bool isRestricted) external onlyOwner {
_restrictedAddresses[contractAddress] = isRestricted;
emit AddressRestricted(contractAddress, isRestricted);
}

/**
Expand All @@ -118,7 +139,7 @@ contract RiskSteward is Ownable, IRiskSteward {
for (uint256 i = 0; i < capsUpdate.length; i++) {
address asset = capsUpdate[i].asset;

if (_restrictedAssets[asset]) revert AssetIsRestricted();
if (_restrictedAddresses[asset]) revert AssetIsRestricted();
if (capsUpdate[i].supplyCap == 0 || capsUpdate[i].borrowCap == 0)
revert InvalidUpdateToZero();

Expand Down Expand Up @@ -156,7 +177,7 @@ contract RiskSteward is Ownable, IRiskSteward {

for (uint256 i = 0; i < ratesUpdate.length; i++) {
address asset = ratesUpdate[i].asset;
if (_restrictedAssets[asset]) revert AssetIsRestricted();
if (_restrictedAddresses[asset]) revert AssetIsRestricted();

(
uint256 currentOptimalUsageRatio,
Expand Down Expand Up @@ -216,7 +237,7 @@ contract RiskSteward is Ownable, IRiskSteward {
for (uint256 i = 0; i < collateralUpdates.length; i++) {
address asset = collateralUpdates[i].asset;

if (_restrictedAssets[asset]) revert AssetIsRestricted();
if (_restrictedAddresses[asset]) revert AssetIsRestricted();
if (collateralUpdates[i].liqProtocolFee != EngineFlags.KEEP_CURRENT)
revert ParamChangeNotAllowed();
if (
Expand Down Expand Up @@ -279,6 +300,74 @@ contract RiskSteward is Ownable, IRiskSteward {
}
}

/**
* @notice method to validate the oracle price caps update
* @param priceCapsUpdate list containing the new price cap params for the oracles
*/
function _validatePriceCapUpdate(PriceCapLstUpdate[] calldata priceCapsUpdate) internal view {
if (priceCapsUpdate.length == 0) revert NoZeroUpdates();

for (uint256 i = 0; i < priceCapsUpdate.length; i++) {
address oracle = priceCapsUpdate[i].oracle;

if (_restrictedAddresses[oracle]) revert OracleIsRestricted();
if (
priceCapsUpdate[i].priceCapUpdateParams.snapshotRatio == 0 ||
priceCapsUpdate[i].priceCapUpdateParams.snapshotTimestamp == 0 ||
priceCapsUpdate[i].priceCapUpdateParams.maxYearlyRatioGrowthPercent == 0
) revert InvalidUpdateToZero();

// get current rate
uint256 currentMaxYearlyGrowthPercent = IPriceCapAdapter(oracle)
.getMaxYearlyGrowthRatePercent();
uint104 currentRatio = IPriceCapAdapter(oracle).getRatio().toUint256().toUint104();

// check that snapshotRatio is less or equal than current one
if (priceCapsUpdate[i].priceCapUpdateParams.snapshotRatio > currentRatio)
revert UpdateNotInRange();

_validateParamUpdate(
ParamUpdateValidationInput({
currentValue: currentMaxYearlyGrowthPercent,
newValue: priceCapsUpdate[i].priceCapUpdateParams.maxYearlyRatioGrowthPercent,
lastUpdated: _timelocks[oracle].priceCapLastUpdated,
riskConfig: _riskConfig.priceCapLst,
isChangeRelative: true
})
);
}
}

/**
* @notice method to validate the oracle stable price caps update
* @param priceCapsUpdate list containing the new price cap values for the oracles
*/
function _validatePriceCapStableUpdate(
PriceCapStableUpdate[] calldata priceCapsUpdate
) internal view {
if (priceCapsUpdate.length == 0) revert NoZeroUpdates();

for (uint256 i = 0; i < priceCapsUpdate.length; i++) {
address oracle = priceCapsUpdate[i].oracle;

if (_restrictedAddresses[oracle]) revert OracleIsRestricted();
if (priceCapsUpdate[i].priceCap == 0) revert InvalidUpdateToZero();

// get current rate
int256 currentPriceCap = IPriceCapAdapterStable(oracle).getPriceCap();

_validateParamUpdate(
ParamUpdateValidationInput({
currentValue: currentPriceCap.toUint256(),
newValue: priceCapsUpdate[i].priceCap,
lastUpdated: _timelocks[oracle].priceCapLastUpdated,
riskConfig: _riskConfig.priceCapStable,
isChangeRelative: true
})
);
}
}

/**
* @notice method to validate the risk param update is within the allowed bound and the debounce is respected
* @param validationParam struct containing values used for validation of the risk param update
Expand Down Expand Up @@ -380,6 +469,36 @@ contract RiskSteward is Ownable, IRiskSteward {
);
}

/**
* @notice method to update the oracle price caps update
* @param priceCapsUpdate list containing the new price cap params for the oracles
*/
function _updateLstPriceCaps(PriceCapLstUpdate[] calldata priceCapsUpdate) internal {
for (uint256 i = 0; i < priceCapsUpdate.length; i++) {
address oracle = priceCapsUpdate[i].oracle;

_timelocks[oracle].priceCapLastUpdated = uint40(block.timestamp);

IPriceCapAdapter(oracle).setCapParameters(priceCapsUpdate[i].priceCapUpdateParams);

if (IPriceCapAdapter(oracle).isCapped()) revert InvalidPriceCapUpdate();
}
}

/**
* @notice method to update the oracle stable price caps update
* @param priceCapsUpdate list containing the new price cap values for the oracles
*/
function _updateStablePriceCaps(PriceCapStableUpdate[] calldata priceCapsUpdate) internal {
for (uint256 i = 0; i < priceCapsUpdate.length; i++) {
address oracle = priceCapsUpdate[i].oracle;

_timelocks[oracle].priceCapLastUpdated = uint40(block.timestamp);

IPriceCapAdapterStable(oracle).setPriceCap(priceCapsUpdate[i].priceCap.toInt256());
}
}

/**
* @notice method to fetch the current interest rate params of the asset
* @param asset the address of the underlying asset
Expand Down Expand Up @@ -434,6 +553,7 @@ contract RiskSteward is Ownable, IRiskSteward {
// we calculate the max permitted difference using the maxPercentChange and the from value, otherwise if the maxPercentChange is absolute in value
// the max permitted difference is the maxPercentChange itself
uint256 maxDiff = isChangeRelative ? (maxPercentChange * from) / BPS_MAX : maxPercentChange;

if (diff > maxDiff) return false;
return true;
}
Expand Down
64 changes: 55 additions & 9 deletions src/interfaces/IRiskSteward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
import {IPoolDataProvider} from 'aave-address-book/AaveV3.sol';
import {EngineFlags} from 'aave-helpers/v3-config-engine/EngineFlags.sol';
import {IAaveV3ConfigEngine as IEngine} from 'aave-v3-origin/periphery/contracts/v3-config-engine/AaveV3ConfigEngine.sol';
import {IPriceCapAdapter} from 'aave-capo/interfaces/IPriceCapAdapter.sol';

/**
* @title IRiskSteward
Expand Down Expand Up @@ -41,17 +42,27 @@ interface IRiskSteward {
*/
error AssetIsRestricted();

/**
* @notice The steward does not allow updates of cap param of a restricted oracle
*/
error OracleIsRestricted();

/**
* @notice Setting the risk parameter value to zero is not allowed
*/
error InvalidUpdateToZero();

/**
* @notice Emitted when the owner configures an asset as restricted to be used by steward
* @param asset address of the underlying asset
* @notice Setting the price cap to be capping the value is not allowed
*/
error InvalidPriceCapUpdate();

/**
* @notice Emitted when the owner configures an asset/oracle as restricted to be used by steward
* @param contractAddress address of the underlying asset or oracle
* @param isRestricted true if asset is set as restricted, false otherwise
*/
event AssetRestricted(address indexed asset, bool indexed isRestricted);
event AddressRestricted(address indexed contractAddress, bool indexed isRestricted);

/**
* @notice Emitted when the risk configuration for the risk params has been set
Expand All @@ -73,6 +84,7 @@ interface IRiskSteward {
uint40 variableRateSlope1LastUpdated;
uint40 variableRateSlope2LastUpdated;
uint40 optimalUsageRatioLastUpdated;
uint40 priceCapLastUpdated;
}

/**
Expand Down Expand Up @@ -113,6 +125,24 @@ interface IRiskSteward {
RiskParamConfig variableRateSlope1;
RiskParamConfig variableRateSlope2;
RiskParamConfig optimalUsageRatio;
RiskParamConfig priceCapLst;
RiskParamConfig priceCapStable;
}

/**
* @notice Struct used to update the LST cap params
*/
struct PriceCapLstUpdate {
address oracle;
IPriceCapAdapter.PriceCapUpdateParams priceCapUpdateParams;
}

/**
* @notice Struct used to update the stable cap params
*/
struct PriceCapStableUpdate {
address oracle;
uint256 priceCap;
}

/**
Expand Down Expand Up @@ -155,18 +185,34 @@ interface IRiskSteward {
function updateCollateralSide(IEngine.CollateralUpdate[] calldata collateralUpdates) external;

/**
* @notice method to check if an asset is restricted to be used by the risk stewards
* @param asset address of the underlying asset
* @notice Allows updating lst price cap params across multiple oracles
* @dev A price cap update is only possible after minDelay has passed after last update
* @dev A price cap increase / decrease is only allowed by a magnitude of maxPercentChange
* @param priceCapUpdates struct containing new price cap params to be updated
*/
function updateLstPriceCaps(PriceCapLstUpdate[] calldata priceCapUpdates) external;

/**
* @notice Allows updating price cap params across multiple oracles
* @dev A price cap update is only possible after minDelay has passed after last update
* @dev A price cap increase / decrease is only allowed by a magnitude of maxPercentChange
* @param priceCapUpdates struct containing new price cap params to be updated
*/
function updateStablePriceCaps(PriceCapStableUpdate[] calldata priceCapUpdates) external;

/**
* @notice method to check if an asset/oracle is restricted to be used by the risk stewards
* @param contractAddress address of the underlying asset or oracle
* @return bool if asset is restricted or not
*/
function isAssetRestricted(address asset) external view returns (bool);
function isAddressRestricted(address contractAddress) external view returns (bool);

/**
* @notice method called by the owner to set an asset as restricted
* @param asset address of the underlying asset
* @notice method called by the owner to set an asset/oracle as restricted
* @param contractAddress address of the underlying asset or oracle
* @param isRestricted true if asset needs to be restricted, false otherwise
*/
function setAssetRestricted(address asset, bool isRestricted) external;
function setAddressRestricted(address contractAddress, bool isRestricted) external;

/**
* @notice Returns the timelock for a specific asset i.e the last updated timestamp
Expand Down
Loading

0 comments on commit 7fbb7b5

Please sign in to comment.