Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: capo updates #4

Merged
merged 19 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
rustboyar marked this conversation as resolved.
Show resolved Hide resolved
})
);
}
}

/**
* @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
Loading