Skip to content
Open
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 contracts/Comptroller/ComptrollerInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity 0.8.25;

import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { IDeviationBoundedOracle } from "@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol";

import { VToken } from "../Tokens/VTokens/VToken.sol";
import { VAIControllerInterface } from "../Tokens/VAI/VAIControllerInterface.sol";
Expand Down Expand Up @@ -132,6 +133,8 @@ interface ComptrollerInterface {

function oracle() external view returns (ResilientOracleInterface);

function deviationBoundedOracle() external view returns (IDeviationBoundedOracle);

function getAccountLiquidity(address) external view returns (uint, uint, uint);

function getAssetsIn(address) external view returns (VToken[] memory);
Expand Down
6 changes: 6 additions & 0 deletions contracts/Comptroller/ComptrollerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity 0.8.25;

import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { IDeviationBoundedOracle } from "@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol";
import { PoolMarketId } from "./Types/PoolMarketId.sol";

import { VToken } from "../Tokens/VTokens/VToken.sol";
Expand Down Expand Up @@ -328,3 +329,8 @@ contract ComptrollerV18Storage is ComptrollerV17Storage {
/// @notice Whether flash loans are paused system-wide
bool public flashLoanPaused;
}

contract ComptrollerV19Storage is ComptrollerV18Storage {
/// @notice DeviationBoundedOracle for conservative pricing in CF path
IDeviationBoundedOracle public deviationBoundedOracle;
}
6 changes: 3 additions & 3 deletions contracts/Comptroller/Diamond/Diamond.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ pragma solidity 0.8.25;

import { IDiamondCut } from "./interfaces/IDiamondCut.sol";
import { Unitroller } from "../Unitroller.sol";
import { ComptrollerV18Storage } from "../ComptrollerStorage.sol";
import { ComptrollerV19Storage } from "../ComptrollerStorage.sol";

/**
* @title Diamond
* @author Venus
* @notice This contract contains functions related to facets
*/
contract Diamond is IDiamondCut, ComptrollerV18Storage {
contract Diamond is IDiamondCut, ComptrollerV19Storage {
/// @notice Emitted when functions are added, replaced or removed to facets
event DiamondCut(IDiamondCut.FacetCut[] _diamondCut);

Expand Down Expand Up @@ -72,7 +72,7 @@ contract Diamond is IDiamondCut, ComptrollerV18Storage {
*/
function facetAddress(
bytes4 functionSelector
) external view returns (ComptrollerV18Storage.FacetAddressAndPosition memory) {
) external view returns (ComptrollerV19Storage.FacetAddressAndPosition memory) {
return _selectorToFacetAndPosition[functionSelector];
}

Expand Down
108 changes: 69 additions & 39 deletions contracts/Comptroller/Diamond/facets/FacetBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { VToken } from "../../../Tokens/VTokens/VToken.sol";
import { ComptrollerErrorReporter } from "../../../Utils/ErrorReporter.sol";
import { ExponentialNoError } from "../../../Utils/ExponentialNoError.sol";
import { IVAIVault, Action } from "../../../Comptroller/ComptrollerInterface.sol";
import { ComptrollerV18Storage } from "../../../Comptroller/ComptrollerStorage.sol";
import { ComptrollerV19Storage } from "../../../Comptroller/ComptrollerStorage.sol";
import { IDeviationBoundedOracle } from "@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol";
import { PoolMarketId } from "../../../Comptroller/Types/PoolMarketId.sol";
import { IFacetBase, WeightFunction } from "../interfaces/IFacetBase.sol";

Expand All @@ -19,7 +20,7 @@ import { IFacetBase, WeightFunction } from "../interfaces/IFacetBase.sol";
* @author Venus
* @notice This facet contract contains functions related to access and checks
*/
contract FacetBase is IFacetBase, ComptrollerV18Storage, ExponentialNoError, ComptrollerErrorReporter {
contract FacetBase is IFacetBase, ComptrollerV19Storage, ExponentialNoError, ComptrollerErrorReporter {
using SafeERC20 for IERC20;

/// @notice The initial Venus index for a market
Expand Down Expand Up @@ -134,26 +135,69 @@ contract FacetBase is IFacetBase, ComptrollerV18Storage, ExponentialNoError, Com
}

/**
* @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed
* @param vTokenModify The market to hypothetically redeem/borrow in
* @notice Computes hypothetical account liquidity after applying the given redeem/borrow amounts.
* Use this in state-changing contexts (hooks, claim flows, pool selection).
* When `weightingStrategy` is USE_COLLATERAL_FACTOR, calls `_updateProtectionStates` before
* delegating to the lens, which updates the bounded price and enables protection if the deviation
* exceeds the threshold.
* @param account The account to determine liquidity for
* @param redeemTokens The number of tokens to hypothetically redeem
* @param vTokenModify The market to hypothetically redeem/borrow in
* @param redeemTokens The number of vTokens to hypothetically redeem
* @param borrowAmount The amount of underlying to hypothetically borrow
* @param weightingStrategy The weighting strategy to use:
* - `WeightFunction.USE_COLLATERAL_FACTOR` to use collateral factor
* - `WeightFunction.USE_LIQUIDATION_THRESHOLD` to use liquidation threshold
* @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data,
* without calculating accumulated interest.
* @return (possible error code,
hypothetical account liquidity in excess of collateral requirements,
* hypothetical account shortfall below collateral requirements)
* @param weightingStrategy USE_COLLATERAL_FACTOR for borrow/entry checks; USE_LIQUIDATION_THRESHOLD for liquidation checks
* @return err Possible error code
* @return liquidity Hypothetical excess liquidity above collateral requirements (0 if in shortfall)
* @return shortfall Hypothetical shortfall below collateral requirements (0 if solvent)
*/
function getHypotheticalAccountLiquidityInternal(
address account,
VToken vTokenModify,
uint256 redeemTokens,
uint256 borrowAmount,
WeightFunction weightingStrategy
) internal returns (Error, uint256, uint256) {
// Populate the DBO transient price cache (EIP-1153) for all entered assets.
// Required on the CF path so bounded prices are computed once and reused
// by view functions (e.g. getBoundedCollateralPriceView / getBoundedDebtPriceView)
// within the same transaction.
if (weightingStrategy == WeightFunction.USE_COLLATERAL_FACTOR) {
_updateProtectionStates(account);
}
(uint256 err, uint256 liquidity, uint256 shortfall) = comptrollerLens.getHypotheticalAccountLiquidity(
address(this),
account,
vTokenModify,
redeemTokens,
borrowAmount,
weightingStrategy
);
return (Error(err), liquidity, shortfall);
}

/**
* @notice View-only variant: reads bounded prices from the DeviationBoundedOracle without updating
* the transient cache. Use this only in external view functions where state mutation is not
* permitted. On write paths use `getHypotheticalAccountLiquidityInternal` instead.
* @dev If `getHypotheticalAccountLiquidityInternal` (or any code that calls `_updateProtectionStates`)
* was already executed earlier in the same transaction, the DBO transient cache will already be
* populated and this function will read from it gas-efficiently — no recomputation occurs.
* If called in a standalone view context (cold cache), the DBO must compute bounded prices from
* scratch on each call; results are still correct but cost more gas.
* @param account The account to determine liquidity for
* @param vTokenModify The market to hypothetically redeem/borrow in
* @param redeemTokens The number of vTokens to hypothetically redeem
* @param borrowAmount The amount of underlying to hypothetically borrow
* @param weightingStrategy USE_COLLATERAL_FACTOR for borrow/entry checks; USE_LIQUIDATION_THRESHOLD for liquidation checks
* @return err Possible error code
* @return liquidity Hypothetical excess liquidity above collateral requirements (0 if in shortfall)
* @return shortfall Hypothetical shortfall below collateral requirements (0 if solvent)
*/
function getHypotheticalAccountLiquidityInternalView(
address account,
VToken vTokenModify,
uint256 redeemTokens,
uint256 borrowAmount,
WeightFunction weightingStrategy
) internal view returns (Error, uint256, uint256) {
(uint256 err, uint256 liquidity, uint256 shortfall) = comptrollerLens.getHypotheticalAccountLiquidity(
address(this),
Expand Down Expand Up @@ -200,11 +244,7 @@ contract FacetBase is IFacetBase, ComptrollerV18Storage, ExponentialNoError, Com
* @param redeemTokens Amount of tokens to redeem
* @return Success indicator for redeem is allowed or not
*/
function redeemAllowedInternal(
address vToken,
address redeemer,
uint256 redeemTokens
) internal view returns (uint256) {
function redeemAllowedInternal(address vToken, address redeemer, uint256 redeemTokens) internal returns (uint256) {
ensureListed(getCorePoolMarket(vToken));
/* If the redeemer is not 'in' the market, then we can bypass the liquidity check */
if (!getCorePoolMarket(vToken).accountMembership[redeemer]) {
Expand Down Expand Up @@ -260,27 +300,17 @@ contract FacetBase is IFacetBase, ComptrollerV18Storage, ExponentialNoError, Com
}

/**
* @notice Determine the current account liquidity wrt collateral requirements
* @param account The account to get liquidity for
* @param weightingStrategy The weighting strategy to use:
* - `WeightFunction.USE_COLLATERAL_FACTOR` to use collateral factor
* - `WeightFunction.USE_LIQUIDATION_THRESHOLD` to use liquidation threshold
* @return (possible error code (semi-opaque),
* account liquidity in excess of collateral requirements,
* account shortfall below collateral requirements)
* @notice Updates the DeviationBoundedOracle protection state for all assets the account has entered
* @dev This populates the transient price cache so subsequent view calls in the same transaction
* are gas-efficient. Should be called before any liquidity calculation in the CF path.
* @param account The account whose collateral assets need protection state updates
*/
function _getAccountLiquidity(
Comment thread
fred-venus marked this conversation as resolved.
address account,
WeightFunction weightingStrategy
) internal view returns (uint256, uint256, uint256) {
(Error err, uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidityInternal(
account,
VToken(address(0)),
0,
0,
weightingStrategy
);

return (uint256(err), liquidity, shortfall);
function _updateProtectionStates(address account) internal {
IDeviationBoundedOracle boundedOracle = deviationBoundedOracle;
VToken[] memory assets = accountAssets[account];
uint256 len = assets.length;
for (uint256 i; i < len; ++i) {
boundedOracle.updateProtectionState(address(assets[i]));
}
}
}
13 changes: 9 additions & 4 deletions contracts/Comptroller/Diamond/facets/MarketFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,15 @@ contract MarketFacet is IMarketFacet, FacetBase {

userPoolId[msg.sender] = poolId;

(uint256 error, , uint256 shortfall) = _getAccountLiquidity(msg.sender, WeightFunction.USE_COLLATERAL_FACTOR);

if (error != 0 || shortfall > 0) {
revert LiquidityCheckFailed(error, shortfall);
(Error err, , uint256 shortfall) = getHypotheticalAccountLiquidityInternal(
msg.sender,
VToken(address(0)),
0,
0,
WeightFunction.USE_COLLATERAL_FACTOR
);
if (err != Error.NO_ERROR || shortfall > 0) {
revert LiquidityCheckFailed(uint256(err), shortfall);
}
}

Expand Down
25 changes: 20 additions & 5 deletions contracts/Comptroller/Diamond/facets/PolicyFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper {
}
}

if (oracle.getUnderlyingPrice(vToken) == 0) {
(uint256 collateralPrice, uint256 debtPrice) = deviationBoundedOracle.getBoundedPricesView(vToken);
if (collateralPrice == 0 || debtPrice == 0) {
return uint256(Error.PRICE_ERROR);
}

Expand Down Expand Up @@ -258,7 +259,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper {
}

/* The borrower must have shortfall in order to be liquidatable */
(Error err, , uint256 shortfall) = getHypotheticalAccountLiquidityInternal(
(Error err, , uint256 shortfall) = getHypotheticalAccountLiquidityInternalView(
borrower,
VToken(address(0)),
0,
Expand Down Expand Up @@ -426,7 +427,14 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper {
* account shortfall below collateral requirements)
*/
function getBorrowingPower(address account) external view returns (uint256, uint256, uint256) {
return _getAccountLiquidity(account, WeightFunction.USE_COLLATERAL_FACTOR);
(Error err, uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidityInternalView(
account,
VToken(address(0)),
0,
0,
WeightFunction.USE_COLLATERAL_FACTOR
);
return (uint256(err), liquidity, shortfall);
}

/**
Expand All @@ -437,7 +445,14 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper {
* account shortfall below liquidation threshold requirements)
*/
function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256) {
return _getAccountLiquidity(account, WeightFunction.USE_LIQUIDATION_THRESHOLD);
(Error err, uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidityInternalView(
account,
VToken(address(0)),
0,
0,
WeightFunction.USE_LIQUIDATION_THRESHOLD
);
return (uint256(err), liquidity, shortfall);
}

/**
Expand All @@ -456,7 +471,7 @@ contract PolicyFacet is IPolicyFacet, XVSRewardsHelper {
uint256 redeemTokens,
uint256 borrowAmount
) external view returns (uint256, uint256, uint256) {
(Error err, uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidityInternal(
(Error err, uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidityInternalView(
account,
VToken(vTokenModify),
redeemTokens,
Expand Down
27 changes: 27 additions & 0 deletions contracts/Comptroller/Diamond/facets/SetterFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity 0.8.25;

import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { IDeviationBoundedOracle } from "@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol";

import { VToken } from "../../../Tokens/VTokens/VToken.sol";
import { Action } from "../../ComptrollerInterface.sol";
Expand Down Expand Up @@ -42,6 +43,12 @@ contract SetterFacet is ISetterFacet, FacetBase {
/// @notice Emitted when price oracle is changed
event NewPriceOracle(ResilientOracleInterface oldPriceOracle, ResilientOracleInterface newPriceOracle);

/// @notice Emitted when deviation bounded oracle is changed
event NewDeviationBoundedOracle(
IDeviationBoundedOracle oldDeviationBoundedOracle,
IDeviationBoundedOracle newDeviationBoundedOracle
);

/// @notice Emitted when borrow cap for a vToken is changed
event NewBorrowCap(VToken indexed vToken, uint256 newBorrowCap);

Expand Down Expand Up @@ -764,6 +771,26 @@ contract SetterFacet is ISetterFacet, FacetBase {
m.isBorrowAllowed = borrowAllowed;
}

/**
* @notice Sets the DeviationBoundedOracle for conservative CF-path pricing
* @param newDeviationBoundedOracle The new DeviationBoundedOracle contract
* @return uint256 0=success, otherwise a failure
*/
function setDeviationBoundedOracle(
IDeviationBoundedOracle newDeviationBoundedOracle
) external compareAddress(address(deviationBoundedOracle), address(newDeviationBoundedOracle)) returns (uint256) {
ensureAllowed("setDeviationBoundedOracle(address)");

ensureNonzeroAddress(address(newDeviationBoundedOracle));

IDeviationBoundedOracle oldDeviationBoundedOracle = deviationBoundedOracle;
deviationBoundedOracle = newDeviationBoundedOracle;

emit NewDeviationBoundedOracle(oldDeviationBoundedOracle, newDeviationBoundedOracle);

return uint256(Error.NO_ERROR);
}

/**
* @dev Updates the valid price oracle. Used by _setPriceOracle and setPriceOracle
* @param newOracle The new price oracle to be set
Expand Down
3 changes: 3 additions & 0 deletions contracts/Comptroller/Diamond/interfaces/ISetterFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity 0.8.25;

import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { IDeviationBoundedOracle } from "@venusprotocol/oracle/contracts/interfaces/IDeviationBoundedOracle.sol";
import { VToken } from "../../../Tokens/VTokens/VToken.sol";
import { Action } from "../../ComptrollerInterface.sol";
import { VAIControllerInterface } from "../../../Tokens/VAI/VAIControllerInterface.sol";
Expand Down Expand Up @@ -105,4 +106,6 @@ interface ISetterFacet {
function setAllowCorePoolFallback(uint96 poolId, bool allowFallback) external;

function setFlashLoanPaused(bool paused) external;

function setDeviationBoundedOracle(IDeviationBoundedOracle newDeviationBoundedOracle) external returns (uint256);
}
Loading
Loading