22// SPDX-FileCopyrightText: 2022 Venus
33pragma solidity 0.8.25 ;
44
5- import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol " ;
6- import "./interfaces/VBep20Interface.sol " ;
7- import "./interfaces/OracleInterface.sol " ;
8- import "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol " ;
5+ import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol " ;
6+ import { VBep20Interface } from "./interfaces/VBep20Interface.sol " ;
7+ import { OracleInterface, ResilientOracleInterface, BoundValidatorInterface } from "./interfaces/OracleInterface.sol " ;
8+ import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol " ;
9+ import { ICappedOracle } from "./interfaces/ICappedOracle.sol " ;
10+ import { Transient } from "./lib/Transient.sol " ;
911
1012/**
1113 * @title ResilientOracle
@@ -17,8 +19,7 @@ import "@venusprotocol/governance-contracts/contracts/Governance/AccessControlle
1719 * for attacking the protocol.
1820 *
1921 * The Resilient Oracle uses multiple sources and fallback mechanisms to provide accurate prices and protect
20- * the protocol from oracle attacks. Currently it includes integrations with Chainlink, Pyth, Binance Oracle
21- * and TWAP (Time-Weighted Average Price) oracles. TWAP uses PancakeSwap as the on-chain price source.
22+ * the protocol from oracle attacks.
2223 *
2324 * For every market (vToken) we configure the main, pivot and fallback oracles. The oracles are configured per
2425 * vToken's underlying asset address. The main oracle oracle is the most trustworthy price source, the pivot
@@ -36,9 +37,8 @@ anchorRatio = anchorPrice/reporterPrice
3637isValid = anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio
3738```
3839
39- * In most cases, Chainlink is used as the main oracle, TWAP or Pyth oracles are used as the pivot oracle depending
40- * on which supports the given market and Binance oracle is used as the fallback oracle. For some markets we may
41- * use Pyth or TWAP as the main oracle if the token price is not supported by Chainlink or Binance oracles.
40+ * In most cases, Chainlink is used as the main oracle, other oracles are used as the pivot oracle depending
41+ * on which supports the given market and Binance oracle is used as the fallback oracle.
4242 *
4343 * For a fetched price to be valid it must be positive and not stagnant. If the price is invalid then we consider the
4444 * oracle to be stagnant and treat it like it's disabled.
@@ -66,6 +66,8 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
6666 /// @notice `enableFlagsForOracles` stores the enabled state
6767 /// for each oracle in the same order as `oracles`
6868 bool [3 ] enableFlagsForOracles;
69+ /// @notice `cachingEnabled` is a flag that indicates whether the asset price should be cached
70+ bool cachingEnabled;
6971 }
7072
7173 uint256 public constant INVALID_PRICE = 0 ;
@@ -82,6 +84,12 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
8284 /// and can serve as any underlying asset of a market that supports native tokens
8385 address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB ;
8486
87+ /// @notice Slot to cache the asset's price, used for transient storage
88+ /// custom:storage-location erc7201:venus-protocol/oracle/ResilientOracle/cache
89+ /// keccak256(abi.encode(uint256(keccak256("venus-protocol/oracle/ResilientOracle/cache")) - 1))
90+ /// & ~bytes32(uint256(0xff))
91+ bytes32 public constant CACHE_SLOT = 0x4e99ec55972332f5e0ef9c6623192c0401b609161bffae64d9ccdd7ad6cc7800 ;
92+
8593 /// @notice Bound validator contract address
8694 /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
8795 BoundValidatorInterface public immutable boundValidator;
@@ -101,6 +109,9 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
101109 /// Event emitted when an oracle is enabled or disabled
102110 event OracleEnabled (address indexed asset , uint256 indexed role , bool indexed enable );
103111
112+ /// Event emitted when an asset cachingEnabled flag is set
113+ event CachedEnabled (address indexed asset , bool indexed enabled );
114+
104115 /**
105116 * @notice Checks whether an address is null or not
106117 */
@@ -175,11 +186,8 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
175186 function setTokenConfigs (TokenConfig[] memory tokenConfigs_ ) external {
176187 if (tokenConfigs_.length == 0 ) revert ("length can't be 0 " );
177188 uint256 numTokenConfigs = tokenConfigs_.length ;
178- for (uint256 i; i < numTokenConfigs; ) {
189+ for (uint256 i; i < numTokenConfigs; ++ i ) {
179190 setTokenConfig (tokenConfigs_[i]);
180- unchecked {
181- ++ i;
182- }
183191 }
184192 }
185193
@@ -215,6 +223,7 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
215223 * @custom:access Only Governance
216224 * @custom:error NotNullAddress error is thrown if asset address is null
217225 * @custom:error TokenConfigExistance error is thrown if token config is not set
226+ * @custom:event Emits OracleEnabled event with asset address, role of the oracle and enabled flag
218227 */
219228 function enableOracle (
220229 address asset ,
@@ -227,30 +236,22 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
227236 }
228237
229238 /**
230- * @notice Updates the TWAP pivot oracle price .
239+ * @notice Updates the capped main oracle snapshot .
231240 * @dev This function should always be called before calling getUnderlyingPrice
232241 * @param vToken vToken address
233242 */
234243 function updatePrice (address vToken ) external override {
235244 address asset = _getUnderlyingAsset (vToken);
236- (address pivotOracle , bool pivotOracleEnabled ) = getOracle (asset, OracleRole.PIVOT);
237- if (pivotOracle != address (0 ) && pivotOracleEnabled) {
238- //if pivot oracle is not TwapOracle it will revert so we need to catch the revert
239- try TwapInterface (pivotOracle).updateTwap (asset) {} catch {}
240- }
245+ _updateAssetPrice (asset);
241246 }
242247
243248 /**
244- * @notice Updates the pivot oracle price. Currently using TWAP
249+ * @notice Updates the capped main oracle snapshot.
245250 * @dev This function should always be called before calling getPrice
246251 * @param asset asset address
247252 */
248253 function updateAssetPrice (address asset ) external {
249- (address pivotOracle , bool pivotOracleEnabled ) = getOracle (asset, OracleRole.PIVOT);
250- if (pivotOracle != address (0 ) && pivotOracleEnabled) {
251- //if pivot oracle is not TwapOracle it will revert so we need to catch the revert
252- try TwapInterface (pivotOracle).updateTwap (asset) {} catch {}
253- }
254+ _updateAssetPrice (asset);
254255 }
255256
256257 /**
@@ -302,6 +303,7 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
302303 * @custom:error NotNullAddress is thrown if asset address is null
303304 * @custom:error NotNullAddress is thrown if main-role oracle address for asset is null
304305 * @custom:event Emits TokenConfigAdded event when the asset config is set successfully by the authorized account
306+ * @custom:event Emits CachedEnabled event when the asset cachingEnabled flag is set successfully
305307 */
306308 function setTokenConfig (
307309 TokenConfig memory tokenConfig
@@ -315,6 +317,7 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
315317 tokenConfig.oracles[uint256 (OracleRole.PIVOT)],
316318 tokenConfig.oracles[uint256 (OracleRole.FALLBACK)]
317319 );
320+ emit CachedEnabled (tokenConfig.asset, tokenConfig.cachingEnabled);
318321 }
319322
320323 /**
@@ -329,8 +332,42 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
329332 enabled = tokenConfigs[asset].enableFlagsForOracles[uint256 (role)];
330333 }
331334
335+ /**
336+ * @notice Updates the capped oracle snapshot.
337+ * @dev Cache the asset price and return if already cached
338+ * @param asset asset address
339+ */
340+ function _updateAssetPrice (address asset ) internal {
341+ if (Transient.readCachedPrice (CACHE_SLOT, asset) != 0 ) {
342+ return ;
343+ }
344+
345+ (address mainOracle , bool mainOracleEnabled ) = getOracle (asset, OracleRole.MAIN);
346+ if (mainOracle != address (0 ) && mainOracleEnabled) {
347+ // if main oracle is not CorrelatedTokenOracle it will revert so we need to catch the revert
348+ try ICappedOracle (mainOracle).updateSnapshot () {} catch {}
349+ }
350+
351+ if (_isCacheEnabled (asset)) {
352+ uint256 price = _getPrice (asset);
353+ Transient.cachePrice (CACHE_SLOT, asset, price);
354+ }
355+ }
356+
357+ /**
358+ * @notice Gets price for the provided asset
359+ * @param asset asset address
360+ * @return price USD price in scaled decimal places.
361+ * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid
362+ */
332363 function _getPrice (address asset ) internal view returns (uint256 ) {
333364 uint256 pivotPrice = INVALID_PRICE;
365+ uint256 price;
366+
367+ price = Transient.readCachedPrice (CACHE_SLOT, asset);
368+ if (price != 0 ) {
369+ return price;
370+ }
334371
335372 // Get pivot oracle price, Invalid price if not available or error
336373 (address pivotOracle , bool pivotOracleEnabled ) = getOracle (asset, OracleRole.PIVOT);
@@ -451,4 +488,13 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
451488 asset = VBep20Interface (vToken).underlying ();
452489 }
453490 }
491+
492+ /**
493+ * @dev This function checks if the asset price should be cached
494+ * @param asset asset address
495+ * @return bool true if caching is enabled, false otherwise
496+ */
497+ function _isCacheEnabled (address asset ) private view returns (bool ) {
498+ return tokenConfigs[asset].cachingEnabled;
499+ }
454500}
0 commit comments