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

[VEN-3018]: Initial Risk Steward contract #115

Open
wants to merge 52 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ed3eeb7
feat: add risk steward
coreyar Jan 15, 2025
5504839
chore: fix natspec
coreyar Jan 15, 2025
9dc0613
refactor: use openzepplin paused
coreyar Jan 15, 2025
8d600bb
refactor: add risk param config validation
coreyar Jan 15, 2025
ef66f5e
refactor: add access control manager
coreyar Jan 15, 2025
d29694a
refactor: break out update logic to specific contract
coreyar Jan 16, 2025
a90ee9f
feat: add deployment script
coreyar Jan 16, 2025
1dbe33c
feat: add testnet chaos labs risk oracle
coreyar Jan 20, 2025
46a8f75
refactor: rename market cap update types
coreyar Jan 20, 2025
f24b93c
refactor: processUpdates must come from RiskStewardReceiver
coreyar Jan 21, 2025
56ce494
feat: add RiskSteward deployments
coreyar Jan 21, 2025
147491b
refactor: make riskParameterConfigs public
coreyar Jan 24, 2025
9ef21ee
refactor: update events to include previous values, and toggle config…
coreyar Jan 24, 2025
b34dac1
refactor: move gap to storage definition block
coreyar Jan 24, 2025
14f3668
refactor: check that constructor addresses are nonzero
coreyar Jan 24, 2025
90b22c2
refactor: update natspec
coreyar Jan 24, 2025
a0614ce
refactor: optimize MaxIncreaseBpsUpdated event variables
coreyar Jan 24, 2025
2832d52
chore: :shirt:
coreyar Jan 24, 2025
eb79014
refactor: make update types constants
coreyar Jan 27, 2025
d6615aa
refactor: support lower cap changes
coreyar Jan 27, 2025
d9d7684
feat: updating deployment files
coreyar Jan 27, 2025
c4b8279
fix: RSR-02 | Repeat Calculation
coreyar Feb 6, 2025
b19329a
fix: RSR-01 | User-Defined Getters
coreyar Feb 6, 2025
a774dd6
fix: VPB-01 | Missing Or Incomplete Natspec
coreyar Feb 6, 2025
f41a600
fix: RSV-05 | Unnecessary Imports
coreyar Feb 6, 2025
5e94c1c
fix: RSV-04 | Typos And Inconsistencies
coreyar Feb 6, 2025
63a2850
fix: RSR-07 | Disabled Function Does Nothing
coreyar Feb 6, 2025
52c41ee
fix: RSR-06 | Indexed Dynamic Data Type In Event
coreyar Feb 6, 2025
594f67f
fix: MCR-06 | Unnecessary Inheritance
coreyar Feb 6, 2025
2a9a302
fix: MCR-03 | Event Not Indexed
coreyar Feb 6, 2025
c28304a
fix: MCR-04 | Usage of Magic Numbers
coreyar Feb 6, 2025
e89efde
fix: RSV-03 | Missing Input Validation
coreyar Feb 6, 2025
dbb8aca
fix: RSR-05 | Missing Zero Address Validation
coreyar Feb 6, 2025
665195a
fix: RSV-03 | Missing Input Validation
coreyar Feb 10, 2025
1895ef7
Revert "fix: RSR-07 | Disabled Function Does Nothing"
coreyar Feb 14, 2025
ab5fd54
refactor: revert renounceOwnership
coreyar Feb 14, 2025
11ac996
fix: RIS-1Markets with Zero Caps Cannot Be Updated by the Risk Steward
coreyar Feb 14, 2025
cf13d9f
fix: S1 Code NatSpec Improvements
coreyar Feb 14, 2025
575e255
fix: S2 Upgradeable Contract Storage Gaps
coreyar Feb 14, 2025
4db7862
fix: S3 Gas Optimization in _decodeBytesToUint256()
coreyar Feb 14, 2025
e4558bf
fix: S4 Ownership Can Be Renounced
coreyar Feb 14, 2025
3ca0283
fix: :shirt: lint line length
coreyar Feb 17, 2025
0b927fe
fix: Update storage gap to account for constants
coreyar Feb 24, 2025
3e7ed60
fix: remove extra space
coreyar Feb 24, 2025
0fef56a
fix: ensure latest update always processed
coreyar Feb 24, 2025
25b81d6
chore: lock
coreyar Feb 24, 2025
d5120f9
fix: pin to workspace
coreyar Feb 24, 2025
7f3fd43
Revert "fix: ensure latest update always processed"
coreyar Feb 24, 2025
6ce419a
fix: update mock access control init
coreyar Feb 24, 2025
81beef1
fix: enforce id is the latest per market and type
coreyar Feb 24, 2025
642da42
Merge branch 'develop' into acm-risk-steward
coreyar Feb 25, 2025
b625924
chore: update testnet deployments
coreyar Feb 26, 2025
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: 2 additions & 1 deletion contracts/Cross-chain/BaseOmnichainControllerSrc.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ contract BaseOmnichainControllerSrc is Ownable, Pausable {
* @notice Emitted when the maximum daily limit of commands from the local chain is modified
*/
event SetMaxDailyLimit(uint16 indexed chainId, uint256 oldMaxLimit, uint256 newMaxLimit);
/*

/**
* @notice Emitted when the address of ACM is updated
*/
event NewAccessControlManager(address indexed oldAccessControlManager, address indexed newAccessControlManager);
Expand Down
13 changes: 13 additions & 0 deletions contracts/RiskSteward/IRiskSteward.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

import { RiskParameterUpdate } from "../interfaces/IRiskOracle.sol";
import { ICorePoolComptroller } from "../interfaces/ICorePoolComptroller.sol";

interface IRiskSteward {
function CORE_POOL_COMPTROLLER() external view returns (ICorePoolComptroller);

function initialize(address accessControlManager_, uint256 maxIncreaseBps_) external;

function processUpdate(RiskParameterUpdate calldata update) external;
}
23 changes: 23 additions & 0 deletions contracts/RiskSteward/IRiskStewardReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { IRiskOracle } from "../interfaces/IRiskOracle.sol";

struct RiskParamConfig {
bool active;
uint256 debounce;
address riskSteward;
}

interface IRiskStewardReceiver {
function RISK_ORACLE() external view returns (IRiskOracle);

function initialize(address accessControlManager_) external;

function setRiskParameterConfig(string calldata updateType, address riskSteward, uint256 debounce) external;

function toggleConfigActive(string calldata updateType) external;

function processUpdateById(uint256 updateId) external;

function processUpdateByParameterAndMarket(string memory updateType, address market) external;
}
282 changes: 282 additions & 0 deletions contracts/RiskSteward/MarketCapsRiskSteward.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { RiskParameterUpdate } from "../interfaces/IRiskOracle.sol";
import { IVToken } from "../interfaces/IVToken.sol";
import { ICorePoolComptroller } from "../interfaces/ICorePoolComptroller.sol";
import { IIsolatedPoolsComptroller } from "../interfaces/IIsolatedPoolsComptroller.sol";
import { IRiskStewardReceiver } from "./IRiskStewardReceiver.sol";
import { AccessControlledV8 } from "../Governance/AccessControlledV8.sol";
import { IRiskSteward } from "./IRiskSteward.sol";
import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol";

/**
* @title MarketCapsRiskSteward
* @author Venus
* @notice Contract that can update supply and borrow caps received from RiskStewardReceiver. Requires that the update is within the max delta.
* Expects the new value to be an encoded uint256 value of un padded bytes.
* @custom:security-contact https://github.com/VenusProtocol/governance-contracts#discussion
*/
contract MarketCapsRiskSteward is IRiskSteward, AccessControlledV8 {
/// @dev Max basis points i.e., 100%
uint256 private constant MAX_BPS = 10000;

/**
* @notice The max delta bps for the update relative to the current value
*/
uint256 public maxDeltaBps;

/**
* @notice Address of the CorePoolComptroller used for selecting the correct comptroller abi
*/
ICorePoolComptroller public immutable CORE_POOL_COMPTROLLER;

/**
* @notice Address of the RiskStewardReceiver used to validate incoming updates
*/
IRiskStewardReceiver public immutable RISK_STEWARD_RECEIVER;

/**
* @notice The update type for supply caps
*/
string public constant SUPPLY_CAP = "supplyCap";

/**
* @notice The update type for borrow caps
*/
string public constant BORROW_CAP = "borrowCap";

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;

/**
* @notice Emitted when a supply cap is updated
*/
event SupplyCapUpdated(address indexed market, uint256 indexed newSupplyCap);

/**
* @notice Emitted when a borrow cap is updated
*/
event BorrowCapUpdated(address indexed market, uint256 indexed newBorrowCap);

/**
* @notice Emitted when the max delta bps is updated
*/
event MaxDeltaBpsUpdated(uint256 oldMaxDeltaBps, uint256 indexed newMaxDeltaBps);

/**
* @notice Thrown when a maxDeltaBps value of 0 is set
*/
error InvalidMaxDeltaBps();

/**
* @notice Thrown when an update type that is not supported is operated on
*/
error UnsupportedUpdateType();

/**
* @notice Thrown when the new value of an update is out of range
*/
error UpdateNotInRange();

/**
* @notice Thrown when the update is not coming from the RiskStewardReceiver
*/
error OnlyRiskStewardReceiver();

/**
* @dev Sets the immutable CorePoolComptroller and RiskStewardReceiver addresses and disables initializers
* @param corePoolComptroller_ The address of the CorePoolComptroller
* @param riskStewardReceiver_ The address of the RiskStewardReceiver
* @custom:error Throws ZeroAddressNotAllowed if the CorePoolComptroller or RiskStewardReceiver addresses are zero
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(address corePoolComptroller_, address riskStewardReceiver_) {
ensureNonzeroAddress(corePoolComptroller_);
ensureNonzeroAddress(riskStewardReceiver_);
CORE_POOL_COMPTROLLER = ICorePoolComptroller(corePoolComptroller_);
RISK_STEWARD_RECEIVER = IRiskStewardReceiver(riskStewardReceiver_);
_disableInitializers();
}

/**
* @dev Initializes the contract as ownable, access controlled, and pausable. Sets the max delta bps initial value.
* @param accessControlManager_ The address of the access control manager
* @param maxDeltaBps_ The max detla bps
* @custom:error Throws InvalidMaxDeltaBps if the max delta bps is 0 or greater than MAX_BPS
*/
function initialize(address accessControlManager_, uint256 maxDeltaBps_) external initializer {
__AccessControlled_init(accessControlManager_);
if (maxDeltaBps_ == 0 || maxDeltaBps_ > MAX_BPS) {
revert InvalidMaxDeltaBps();
}
maxDeltaBps = maxDeltaBps_;
}

/**
* @notice Sets the max delta bps
* @param maxDeltaBps_ The new max delta bps
* @custom:event Emits MaxDeltaBpsUpdated with the old and new max delta bps
* @custom:error InvalidMaxDeltaBps if the max delta bps is 0 or greater than MAX_BPS
* @custom:access Controlled by AccessControlManager
*/
function setMaxDeltaBps(uint256 maxDeltaBps_) external {
_checkAccessAllowed("setMaxDeltaBps(uint256)");
if (maxDeltaBps_ == 0 || maxDeltaBps_ > MAX_BPS) {
revert InvalidMaxDeltaBps();
}
emit MaxDeltaBpsUpdated(maxDeltaBps, maxDeltaBps_);
maxDeltaBps = maxDeltaBps_;
}

/**
* @notice Processes a market cap update from the RiskStewardReceiver.
* Validates that the update is within range and then directly update the market supply or borrow cap on the market's comptroller.
* @param update RiskParameterUpdate update to process.
* @custom:error OnlyRiskStewardReceiver Thrown if the sender is not the RiskStewardReceiver
* @custom:error UnsupportedUpdateType Thrown if the update type is not supported
* @custom:error UpdateNotInRange Thrown if the update is not within the allowed range
* @custom:event Emits SupplyCapUpdated or BorrowCapUpdated depending on the update with the market and new cap
* @custom:access Only callable by the RiskStewardReceiver
*/
function processUpdate(RiskParameterUpdate calldata update) external {
if (msg.sender != address(RISK_STEWARD_RECEIVER)) {
revert OnlyRiskStewardReceiver();
}
if (Strings.equal(update.updateType, SUPPLY_CAP)) {
_processSupplyCapUpdate(update);
} else if (Strings.equal(update.updateType, BORROW_CAP)) {
_processBorrowCapUpdate(update);
} else {
revert UnsupportedUpdateType();
}
}

/**
* @notice Updates the supply cap for the given market.
* @param market The market to update the supply cap for
* @param newValue The new supply cap value
* @custom:event Emits SupplyCapUpdated with the market and new supply cap
*/
function _updateSupplyCaps(address market, uint256 newValue) internal {
address comptroller = IVToken(market).comptroller();
address[] memory newSupplyCapMarkets = new address[](1);
newSupplyCapMarkets[0] = market;
uint256[] memory newSupplyCaps = new uint256[](1);
newSupplyCaps[0] = newValue;
if (comptroller == address(CORE_POOL_COMPTROLLER)) {
ICorePoolComptroller(comptroller)._setMarketSupplyCaps(newSupplyCapMarkets, newSupplyCaps);
} else {
IIsolatedPoolsComptroller(comptroller).setMarketSupplyCaps(newSupplyCapMarkets, newSupplyCaps);
}
emit SupplyCapUpdated(market, newSupplyCaps[0]);
}

/**
* @notice Updates the borrow cap for the given market.
* @param market The market to update the borrow cap for
* @param newValue The new borrow cap value
* @custom:event Emits BorrowCapUpdated with the market and new borrow cap
*/
function _updateBorrowCaps(address market, uint256 newValue) internal {
address comptroller = IVToken(market).comptroller();
address[] memory newBorrowCapMarkets = new address[](1);
newBorrowCapMarkets[0] = market;
uint256[] memory newBorrowCaps = new uint256[](1);
newBorrowCaps[0] = newValue;
if (comptroller == address(CORE_POOL_COMPTROLLER)) {
ICorePoolComptroller(comptroller)._setMarketBorrowCaps(newBorrowCapMarkets, newBorrowCaps);
} else {
IIsolatedPoolsComptroller(comptroller).setMarketBorrowCaps(newBorrowCapMarkets, newBorrowCaps);
}
emit BorrowCapUpdated(market, newBorrowCaps[0]);
}

/**
* @notice Validates the new supply cap and if valid, updates the supply cap for the given market.
* @param update RiskParameterUpdate update to process
* @custom:event Emits SupplyCapUpdated with the market and new supply cap
* @custom:error UpdateNotInRange if the update is not within the allowed range
*/
function _processSupplyCapUpdate(RiskParameterUpdate memory update) internal {
uint256 newValue = _decodeBytesToUint256(update.newValue);
_validateSupplyCapUpdate(update.market, newValue);
_updateSupplyCaps(update.market, newValue);
}

/**
* @notice Validates the new borrow cap and if valid, updates the borrow cap for the given market.
* @param update RiskParameterUpdate update to process
* @custom:event Emits BorrowCapUpdated with the market and new borrow cap
* @custom:error UpdateNotInRange if the update is not within the allowed range
*/
function _processBorrowCapUpdate(RiskParameterUpdate memory update) internal {
uint256 newValue = _decodeBytesToUint256(update.newValue);
_validateBorrowCapUpdate(update.market, newValue);
_updateBorrowCaps(update.market, newValue);
}

/**
* @notice Checks that the new supply cap is within the allowed range of the current supply cap.
* @param market The market to validate the supply cap for
* @param newValue The new supply cap value
* @custom:error UpdateNotInRange if the update is not within the allowed range
*/
function _validateSupplyCapUpdate(address market, uint256 newValue) internal view {
ICorePoolComptroller comptroller = ICorePoolComptroller(IVToken(market).comptroller());
uint256 currentSupplyCap = comptroller.supplyCaps(market);

_updateWithinAllowedRange(currentSupplyCap, newValue);
}

/**
* @notice Checks that the new borrow cap is within the allowed range of the current borrow cap.
* @param market The market to validate the borrow cap for
* @param newValue The new borrow cap value
* @custom:error UpdateNotInRange if the update is not within the allowed range
*/
function _validateBorrowCapUpdate(address market, uint256 newValue) internal view {
ICorePoolComptroller comptroller = ICorePoolComptroller(IVToken(market).comptroller());
uint256 currentBorrowCap = comptroller.borrowCaps(address(market));

_updateWithinAllowedRange(currentBorrowCap, newValue);
}

/**
* @notice Ensures the risk param update is within the allowed range.
* If the previous value is 0, this means the market is not yet supported or disabled and setting a new value is not allowed.
* @param previousValue current risk param value
* @param newValue new updated risk param value
* @custom:error UpdateNotInRange if the update is not within the allowed range
*/
function _updateWithinAllowedRange(uint256 previousValue, uint256 newValue) internal view {
uint256 diff = newValue > previousValue ? newValue - previousValue : previousValue - newValue;

uint256 maxDiff = (maxDeltaBps * previousValue) / MAX_BPS;

if (diff > maxDiff) {
revert UpdateNotInRange();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not returning anything, as mentioned.


/**
* @notice Decodes un-padded bytes to a uint256
* @param data The un-padded bytes to decode
* @return uint256 The decoded uint256
*/
function _decodeBytesToUint256(bytes memory data) internal pure returns (uint256) {
return abi.decode(abi.encodePacked(new bytes(32 - data.length), data), (uint256));
}

/**
* @dev Disabling renounceOwnership function.
*/
function renounceOwnership() public override {
revert("renounceOwnership() is not allowed");
}
}
Loading