2
2
// SPDX-FileCopyrightText: 2022 Venus
3
3
pragma solidity 0.8.25 ;
4
4
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 " ;
9
11
10
12
/**
11
13
* @title ResilientOracle
@@ -17,8 +19,7 @@ import "@venusprotocol/governance-contracts/contracts/Governance/AccessControlle
17
19
* for attacking the protocol.
18
20
*
19
21
* 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.
22
23
*
23
24
* For every market (vToken) we configure the main, pivot and fallback oracles. The oracles are configured per
24
25
* vToken's underlying asset address. The main oracle oracle is the most trustworthy price source, the pivot
@@ -36,9 +37,8 @@ anchorRatio = anchorPrice/reporterPrice
36
37
isValid = anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio
37
38
```
38
39
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.
42
42
*
43
43
* For a fetched price to be valid it must be positive and not stagnant. If the price is invalid then we consider the
44
44
* oracle to be stagnant and treat it like it's disabled.
@@ -66,6 +66,8 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
66
66
/// @notice `enableFlagsForOracles` stores the enabled state
67
67
/// for each oracle in the same order as `oracles`
68
68
bool [3 ] enableFlagsForOracles;
69
+ /// @notice `cachingEnabled` is a flag that indicates whether the asset price should be cached
70
+ bool cachingEnabled;
69
71
}
70
72
71
73
uint256 public constant INVALID_PRICE = 0 ;
@@ -82,6 +84,12 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
82
84
/// and can serve as any underlying asset of a market that supports native tokens
83
85
address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB ;
84
86
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
+
85
93
/// @notice Bound validator contract address
86
94
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
87
95
BoundValidatorInterface public immutable boundValidator;
@@ -101,6 +109,9 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
101
109
/// Event emitted when an oracle is enabled or disabled
102
110
event OracleEnabled (address indexed asset , uint256 indexed role , bool indexed enable );
103
111
112
+ /// Event emitted when an asset cachingEnabled flag is set
113
+ event CachedEnabled (address indexed asset , bool indexed enabled );
114
+
104
115
/**
105
116
* @notice Checks whether an address is null or not
106
117
*/
@@ -175,11 +186,8 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
175
186
function setTokenConfigs (TokenConfig[] memory tokenConfigs_ ) external {
176
187
if (tokenConfigs_.length == 0 ) revert ("length can't be 0 " );
177
188
uint256 numTokenConfigs = tokenConfigs_.length ;
178
- for (uint256 i; i < numTokenConfigs; ) {
189
+ for (uint256 i; i < numTokenConfigs; ++ i ) {
179
190
setTokenConfig (tokenConfigs_[i]);
180
- unchecked {
181
- ++ i;
182
- }
183
191
}
184
192
}
185
193
@@ -215,6 +223,7 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
215
223
* @custom:access Only Governance
216
224
* @custom:error NotNullAddress error is thrown if asset address is null
217
225
* @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
218
227
*/
219
228
function enableOracle (
220
229
address asset ,
@@ -227,30 +236,22 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
227
236
}
228
237
229
238
/**
230
- * @notice Updates the TWAP pivot oracle price .
239
+ * @notice Updates the capped main oracle snapshot .
231
240
* @dev This function should always be called before calling getUnderlyingPrice
232
241
* @param vToken vToken address
233
242
*/
234
243
function updatePrice (address vToken ) external override {
235
244
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);
241
246
}
242
247
243
248
/**
244
- * @notice Updates the pivot oracle price. Currently using TWAP
249
+ * @notice Updates the capped main oracle snapshot.
245
250
* @dev This function should always be called before calling getPrice
246
251
* @param asset asset address
247
252
*/
248
253
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);
254
255
}
255
256
256
257
/**
@@ -302,6 +303,7 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
302
303
* @custom:error NotNullAddress is thrown if asset address is null
303
304
* @custom:error NotNullAddress is thrown if main-role oracle address for asset is null
304
305
* @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
305
307
*/
306
308
function setTokenConfig (
307
309
TokenConfig memory tokenConfig
@@ -315,6 +317,7 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
315
317
tokenConfig.oracles[uint256 (OracleRole.PIVOT)],
316
318
tokenConfig.oracles[uint256 (OracleRole.FALLBACK)]
317
319
);
320
+ emit CachedEnabled (tokenConfig.asset, tokenConfig.cachingEnabled);
318
321
}
319
322
320
323
/**
@@ -329,8 +332,42 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
329
332
enabled = tokenConfigs[asset].enableFlagsForOracles[uint256 (role)];
330
333
}
331
334
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
+ */
332
363
function _getPrice (address asset ) internal view returns (uint256 ) {
333
364
uint256 pivotPrice = INVALID_PRICE;
365
+ uint256 price;
366
+
367
+ price = Transient.readCachedPrice (CACHE_SLOT, asset);
368
+ if (price != 0 ) {
369
+ return price;
370
+ }
334
371
335
372
// Get pivot oracle price, Invalid price if not available or error
336
373
(address pivotOracle , bool pivotOracleEnabled ) = getOracle (asset, OracleRole.PIVOT);
@@ -451,4 +488,13 @@ contract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOr
451
488
asset = VBep20Interface (vToken).underlying ();
452
489
}
453
490
}
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
+ }
454
500
}
0 commit comments