Skip to content

Commit a424993

Browse files
authored
Merge pull request #239 from VenusProtocol/feat/transient-storage
[VEN-2999][VEN-3002]: add transient storage to Resilient oracle and Capped oracles
2 parents 3cf07f5 + 679f384 commit a424993

File tree

392 files changed

+82878
-63391
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

392 files changed

+82878
-63391
lines changed

.github/workflows/cd.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ jobs:
2121
cache: "yarn"
2222

2323
- name: Install dependencies
24-
# Hack to get around failing "ethereumjs-abi The remote archive doesn't match the expected checksum" error
25-
run: YARN_CHECKSUM_BEHAVIOR=update yarn
24+
run: yarn
2625

2726
- name: Build
2827
run: yarn build

.github/workflows/ci.yml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ jobs:
2222
cache: "yarn"
2323

2424
- name: Install dependencies
25-
# Hack to get around failing "ethereumjs-abi The remote archive doesn't match the expected checksum" error
26-
run: YARN_CHECKSUM_BEHAVIOR=update yarn
25+
run: yarn
2726

2827
- name: Compile contract types
2928
run: yarn hardhat compile
@@ -49,8 +48,7 @@ jobs:
4948
cache: "yarn"
5049

5150
- name: Install dependencies
52-
# Hack to get around failing "ethereumjs-abi The remote archive doesn't match the expected checksum" error
53-
run: YARN_CHECKSUM_BEHAVIOR=update yarn
51+
run: yarn
5452

5553
- name: Run hardhat tests coverage
5654
run: yarn hardhat:coverage
@@ -89,8 +87,7 @@ jobs:
8987
cache: "yarn"
9088

9189
- name: Install dependencies
92-
# Hack to get around failing "ethereumjs-abi The remote archive doesn't match the expected checksum" error
93-
run: YARN_CHECKSUM_BEHAVIOR=update yarn
90+
run: yarn
9491

9592
- name: Verify deployments work
9693
run: yarn hardhat deploy
@@ -114,8 +111,7 @@ jobs:
114111
cache: "yarn"
115112

116113
- name: Install dependencies
117-
# Hack to get around failing "ethereumjs-abi The remote archive doesn't match the expected checksum" error
118-
run: YARN_CHECKSUM_BEHAVIOR=update yarn
114+
run: yarn
119115

120116
- name: Export deployments
121117
run: |

.solhint.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"const-name-snakecase": "warn",
88
"constructor-syntax": "error",
99
"func-visibility": ["error", { "ignoreConstructors": true }],
10-
"max-line-length": ["error", 120],
10+
"max-line-length": ["error", 175],
1111
"not-rely-on-time": "warn",
1212
"reason-string": ["warn", { "maxLength": 64 }],
1313
"ordering": "error"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Resilient Price Feeds is a set of smart contracts that uses multiple oracles and
66

77
DeFi protocols are vulnerable to incorrectly reported prices which can lead to lost money. A price oracle can be manipulated,fail, or suffer other attacks depending on the type of price oracle. It creates a single point of failure, opening attack vectors to the protocol if not mitigated.
88

9-
The Resilient Price Feeds uses multiple oracle sources and fallback mechanisms to return accurate prices and protect from oracle failures. Currently, it includes integrations with Chainlink, RedStone, Pyth and Binance Oracle oracles.
9+
The Resilient Price Feeds uses multiple oracle sources and fallback mechanisms to return accurate prices and protect from oracle failures. Currently, it includes integrations with Chainlink, RedStone and Binance Oracle oracles.
1010

1111
## Details
1212

@@ -21,7 +21,7 @@ anchorRatio = anchorPrice/reporterPrice
2121
isValid = anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio
2222
```
2323

24-
The default configuration uses Chainlink as the main oracle, RedStone or Pyth oracle as the pivot oracle depending on which supports the given market and Binance oracle as the fallback oracle. For some markets we may use RedStone or Pyth as the main oracle if the token price is not supported by Chainlink or Binance oracles.
24+
The default configuration uses Chainlink as the main oracle, RedStone oracle as the pivot oracle depending on which supports the given market and Binance oracle as the fallback oracle. For some markets we may use RedStone as the main oracle if the token price is not supported by Chainlink or Binance oracles.
2525

2626
When fetching an oracle price, for the price to be valid it must be positive and not stagnant. If the price is invalid or stagnant it is ignored and a fallback oracle is used.
2727

4.6 MB
Binary file not shown.
460 KB
Binary file not shown.
782 KB
Binary file not shown.

contracts/ResilientOracle.sol

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
// SPDX-FileCopyrightText: 2022 Venus
33
pragma 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
3637
isValid = 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
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
pragma solidity 0.8.25;
3+
4+
interface ICappedOracle {
5+
function updateSnapshot() external;
6+
}

contracts/interfaces/OracleInterface.sol

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ interface ResilientOracleInterface is OracleInterface {
1313
function getUnderlyingPrice(address vToken) external view returns (uint256);
1414
}
1515

16-
interface TwapInterface is OracleInterface {
17-
function updateTwap(address asset) external returns (uint256);
18-
}
19-
2016
interface BoundValidatorInterface {
2117
function validatePriceWithAnchorPrice(
2218
address asset,

0 commit comments

Comments
 (0)