@ubk-labs/ubk-oracle 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -23,10 +23,13 @@ library UBKOracleConstants {
|
|
|
23
23
|
uint256 public constant ORACLE_MIN_VAULT_RATE_WAD = 0.2e18; // 0.2x (20%)
|
|
24
24
|
uint256 public constant ORACLE_MAX_VAULT_RATE_WAD = 3e18; // 3x (300%)
|
|
25
25
|
|
|
26
|
+
uint256 public constant ORACLE_MAX_VAULT_ASSETS_PER_SHARE = 1e36;
|
|
27
|
+
|
|
26
28
|
// -----------------------------------------------------------------------
|
|
27
29
|
// Oracle Staleness Periods
|
|
28
30
|
// -----------------------------------------------------------------------
|
|
29
31
|
uint256 public constant ORACLE_MIN_STALE_PERIOD = 1 hours;
|
|
32
|
+
uint256 public constant ORACLE_DEFAULT_STALE_PERIOD = 24 hours;
|
|
30
33
|
uint256 public constant ORACLE_MAX_STALE_PERIOD = 48 hours;
|
|
31
34
|
|
|
32
35
|
uint256 public constant ORACLE_DEFAULT_STALE_FALLBACK_MULTIPLIER = 2; //2x stale
|
|
@@ -7,17 +7,18 @@ import "@openzeppelin/contracts/interfaces/IERC4626.sol";
|
|
|
7
7
|
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
8
8
|
|
|
9
9
|
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
|
|
10
|
+
import "@ubk-labs/ubk-commons/contracts/abstract/UBKDecimalsBounded.sol";
|
|
10
11
|
|
|
11
12
|
import "../../interfaces/IUBKOracle.sol";
|
|
12
13
|
import "../errors/UBKOracleErrors.sol";
|
|
13
14
|
import "../constants/UBKOracleConstants.sol";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* @title
|
|
17
|
-
* @notice This contract is
|
|
17
|
+
* @title UBKOracle
|
|
18
|
+
* @notice This contract is the canonical implementation of the IUBKOracle interface.
|
|
18
19
|
*
|
|
19
20
|
* @dev
|
|
20
|
-
* The
|
|
21
|
+
* The contract computes normalized 1e18 (WAD) prices for all supported assets,
|
|
21
22
|
* combining manual overrides, ERC4626 vault conversions, and Chainlink feeds.
|
|
22
23
|
*
|
|
23
24
|
* === PRICE RESOLUTION ORDER ===
|
|
@@ -25,6 +26,12 @@ import "../constants/UBKOracleConstants.sol";
|
|
|
25
26
|
* 2. ERC4626 vault-derived (convertToAssets * underlying price)
|
|
26
27
|
* 3️. Chainlink feed (normalized to 1e18)
|
|
27
28
|
*
|
|
29
|
+
* === DECIMAL INVARIANTS ===
|
|
30
|
+
* - Only explicitly registered tokens may be priced or converted.
|
|
31
|
+
* - Token decimals are validated once at registration and MUST lie within [6, 18].
|
|
32
|
+
* - Validated decimals are cached immutably and never queried from token contracts at runtime.
|
|
33
|
+
* - All internal price and value computations assume and rely on these enforced bounds.
|
|
34
|
+
* - ERC-4626 vaults are only supported when vault.decimals() == underlying.decimals(), preventing cross-decimal normalization errors in vault pricing.
|
|
28
35
|
* === SAFETY FEATURES ===
|
|
29
36
|
* - Recursion guard (nested ERC4626 depth ≤ 5)
|
|
30
37
|
* - Chainlink stale-period enforcement
|
|
@@ -39,7 +46,8 @@ import "../constants/UBKOracleConstants.sol";
|
|
|
39
46
|
* - UI / Subgraphs can use `isPriceFresh()` and `getPriceAge()` for safety checks.
|
|
40
47
|
*
|
|
41
48
|
*/
|
|
42
|
-
|
|
49
|
+
|
|
50
|
+
contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
43
51
|
// -----------------------------------------------------------------------
|
|
44
52
|
// Storage
|
|
45
53
|
// -----------------------------------------------------------------------
|
|
@@ -79,6 +87,9 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
79
87
|
/// @notice Mapping to track supported tokens.
|
|
80
88
|
mapping(address => bool) public isSupported;
|
|
81
89
|
|
|
90
|
+
/// @notice Cache that tracks decimals for supported tokens.
|
|
91
|
+
mapping(address => uint8) internal tokenDecimals;
|
|
92
|
+
|
|
82
93
|
// -----------------------------------------------------------------------
|
|
83
94
|
// Constructor & Modifiers
|
|
84
95
|
// -----------------------------------------------------------------------
|
|
@@ -87,10 +98,7 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
87
98
|
* @notice Deploys the Oracle contract.
|
|
88
99
|
* @param _owner The address to assign as the owner (governance or deployer).
|
|
89
100
|
*/
|
|
90
|
-
constructor(address _owner) Ownable(_owner) {
|
|
91
|
-
if (_owner == address(0))
|
|
92
|
-
revert ZeroAddress("UBKOracle::constructor", "owner");
|
|
93
|
-
}
|
|
101
|
+
constructor(address _owner) Ownable(_owner) {}
|
|
94
102
|
|
|
95
103
|
/// @notice Ensures oracle is not paused.
|
|
96
104
|
modifier whenNotPaused() {
|
|
@@ -108,6 +116,12 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
108
116
|
_recursionDepth--;
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
/// @notice Does not allow execution of the decorated function if token is unsupported.
|
|
120
|
+
modifier supportedTokenOnly(address token) {
|
|
121
|
+
if (!isSupported[token]) revert TokenNotSupported(token);
|
|
122
|
+
_;
|
|
123
|
+
}
|
|
124
|
+
|
|
111
125
|
// -----------------------------------------------------------------------
|
|
112
126
|
// Admin / Configuration
|
|
113
127
|
// -----------------------------------------------------------------------
|
|
@@ -123,21 +137,11 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
/**
|
|
126
|
-
* @notice
|
|
140
|
+
* @notice External facing admin function that sets the maximum time (in seconds) a Chainlink feed value is valid.
|
|
127
141
|
* @param period The new fallback staleness threshold. Must be 1.5x of stalePeriod of the same token.
|
|
128
|
-
* @dev Must lie within [UBKOracleConstants.ORACLE_MIN_STALE_PERIOD, UBKOracleConstants.ORACLE_MAX_STALE_PERIOD].
|
|
129
142
|
*/
|
|
130
143
|
function setStalePeriod(address token, uint256 period) external onlyOwner {
|
|
131
|
-
|
|
132
|
-
period < UBKOracleConstants.ORACLE_MIN_STALE_PERIOD ||
|
|
133
|
-
period > UBKOracleConstants.ORACLE_MAX_STALE_PERIOD
|
|
134
|
-
) revert InvalidStalePeriod(period);
|
|
135
|
-
stalePeriod[token] = period;
|
|
136
|
-
fallbackStalePeriod[token] =
|
|
137
|
-
UBKOracleConstants.ORACLE_DEFAULT_STALE_FALLBACK_MULTIPLIER *
|
|
138
|
-
period; //Minimum fallback period should be 2x stalePeriod[token].
|
|
139
|
-
emit StalePeriodUpdated(token, stalePeriod[token]);
|
|
140
|
-
emit FallbackStalePeriodUpdated(token, fallbackStalePeriod[token]);
|
|
144
|
+
_setStalePeriod(token, period);
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
/**
|
|
@@ -172,8 +176,11 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
172
176
|
) external onlyOwner {
|
|
173
177
|
if (vault == address(0))
|
|
174
178
|
revert ZeroAddress("UBKOracle::setVaultRateBounds", "vault");
|
|
175
|
-
if (
|
|
176
|
-
|
|
179
|
+
if (
|
|
180
|
+
minRate == 0 ||
|
|
181
|
+
maxRate <= minRate ||
|
|
182
|
+
maxRate > UBKOracleConstants.ORACLE_MAX_VAULT_RATE_WAD
|
|
183
|
+
) revert InvalidVaultBounds(vault, minRate, maxRate);
|
|
177
184
|
|
|
178
185
|
vaultRateBounds[vault] = VaultRateBounds(minRate, maxRate);
|
|
179
186
|
emit VaultRateBoundsSet(vault, minRate, maxRate);
|
|
@@ -192,6 +199,10 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
192
199
|
) external onlyOwner whenNotPaused {
|
|
193
200
|
if (token == address(0))
|
|
194
201
|
revert ZeroAddress("UBKOracle::setManualPrice", "token");
|
|
202
|
+
if (!isSupported[token]) {
|
|
203
|
+
_addSupportedToken(token);
|
|
204
|
+
}
|
|
205
|
+
|
|
195
206
|
if (
|
|
196
207
|
price < UBKOracleConstants.ORACLE_MIN_ABSOLUTE_PRICE_WAD ||
|
|
197
208
|
price > UBKOracleConstants.ORACLE_MAX_ABSOLUTE_PRICE_WAD
|
|
@@ -226,9 +237,9 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
226
237
|
* @notice Disables manual pricing for a token.
|
|
227
238
|
* @param token The token address.
|
|
228
239
|
*/
|
|
229
|
-
function disableManualPrice(
|
|
230
|
-
|
|
231
|
-
|
|
240
|
+
function disableManualPrice(
|
|
241
|
+
address token
|
|
242
|
+
) external onlyOwner supportedTokenOnly(token) {
|
|
232
243
|
isManual[token] = false;
|
|
233
244
|
emit ManualModeEnabled(token, false);
|
|
234
245
|
}
|
|
@@ -240,9 +251,7 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
240
251
|
* @dev Ensures decimals ≤ 18 and feed returns a nonzero updatedAt value.
|
|
241
252
|
*/
|
|
242
253
|
function setChainlinkFeed(address token, address feed) external onlyOwner {
|
|
243
|
-
|
|
244
|
-
revert ZeroAddress("UBKOracle::setChainlinkFeed", "input");
|
|
245
|
-
if (feed.code.length == 0) revert InvalidFeedContract(feed);
|
|
254
|
+
_validateChainlinkFeed(token, feed);
|
|
246
255
|
|
|
247
256
|
AggregatorV3Interface agg = AggregatorV3Interface(feed);
|
|
248
257
|
uint8 decimals = agg.decimals();
|
|
@@ -269,18 +278,13 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
269
278
|
* @notice Registers an ERC4626 vault and its underlying asset.
|
|
270
279
|
* @param vault ERC4626 vault token.
|
|
271
280
|
* @param underlying Reference underlying asset used for pricing.
|
|
272
|
-
* @dev
|
|
273
|
-
* (e.g., sUSDe → USDC pegging for depeg protection).
|
|
281
|
+
* @dev Stricly enforces == underlying for flexibility
|
|
274
282
|
*/
|
|
275
283
|
function setERC4626Vault(
|
|
276
284
|
address vault,
|
|
277
285
|
address underlying
|
|
278
286
|
) external onlyOwner {
|
|
279
|
-
|
|
280
|
-
revert ZeroAddress("UBKOracle::setERC4626Vault", "input");
|
|
281
|
-
try IERC4626(vault).asset() returns (address) {} catch {
|
|
282
|
-
revert InvalidERC4626Vault(vault);
|
|
283
|
-
}
|
|
287
|
+
_validateERC4626Vault(vault, underlying);
|
|
284
288
|
erc4626Underlying[vault] = underlying;
|
|
285
289
|
_addSupportedToken(vault);
|
|
286
290
|
emit ERC4626Registered(vault, underlying);
|
|
@@ -307,8 +311,6 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
307
311
|
function fetchAndUpdatePrice(
|
|
308
312
|
address token
|
|
309
313
|
) external whenNotPaused returns (uint256) {
|
|
310
|
-
if (token == address(0))
|
|
311
|
-
revert ZeroAddress("UBKOracle::fetchAndUpdatePrice", "token");
|
|
312
314
|
return _fetchAndUpdatePrice(token);
|
|
313
315
|
}
|
|
314
316
|
|
|
@@ -381,17 +383,28 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
381
383
|
// -----------------------------------------------------------------------
|
|
382
384
|
|
|
383
385
|
/**
|
|
384
|
-
* @notice Converts a token amount into USD (18 decimals)
|
|
385
|
-
* @
|
|
386
|
-
*
|
|
387
|
-
*
|
|
386
|
+
* @notice Converts a supported token amount into USD (18 decimals).
|
|
387
|
+
* @dev
|
|
388
|
+
* Requirements:
|
|
389
|
+
* - `token` MUST be registered as supported by the oracle.
|
|
390
|
+
* - Token decimals are cached at registration and guaranteed to be within [6,18].
|
|
391
|
+
* - Reverts if the token is not supported or if the cached price is stale or unavailable.
|
|
392
|
+
*
|
|
393
|
+
* Behavior:
|
|
394
|
+
* - Returns 0 if `amount == 0` without reading oracle price.
|
|
395
|
+
* - Uses cached decimals and cached price for deterministic gas usage.
|
|
396
|
+
*
|
|
397
|
+
* @param token Supported token address.
|
|
398
|
+
* @param amount Raw token amount in native decimals.
|
|
399
|
+
* @return usdValue USD value with 18 decimals of precision.
|
|
388
400
|
*/
|
|
401
|
+
|
|
389
402
|
function toUSD(
|
|
390
403
|
address token,
|
|
391
404
|
uint256 amount
|
|
392
405
|
) external view returns (uint256 usdValue) {
|
|
393
406
|
if (amount == 0) return 0;
|
|
394
|
-
uint8 decimals =
|
|
407
|
+
uint8 decimals = tokenDecimals[token];
|
|
395
408
|
uint256 normalized = (amount * UBKOracleConstants.WAD) /
|
|
396
409
|
(10 ** decimals);
|
|
397
410
|
uint256 price = _getPrice(token); // 18 decimals
|
|
@@ -399,17 +412,28 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
399
412
|
}
|
|
400
413
|
|
|
401
414
|
/**
|
|
402
|
-
* @notice Converts a USD amount (18 decimals) into token
|
|
403
|
-
* @
|
|
404
|
-
*
|
|
405
|
-
*
|
|
415
|
+
* @notice Converts a USD amount (18 decimals) into units of a supported token.
|
|
416
|
+
* @dev
|
|
417
|
+
* Requirements:
|
|
418
|
+
* - `token` MUST be registered as supported by the oracle.
|
|
419
|
+
* - Token decimals are cached at registration and guaranteed to be within [6,18].
|
|
420
|
+
* - Reverts if the token is not supported or if the cached price is stale or unavailable.
|
|
421
|
+
*
|
|
422
|
+
* Behavior:
|
|
423
|
+
* - Returns 0 if `usdAmount == 0` without reading oracle price.
|
|
424
|
+
* - Uses cached decimals and cached price for deterministic gas usage.
|
|
425
|
+
*
|
|
426
|
+
* @param token Supported token address.
|
|
427
|
+
* @param usdAmount USD value with 18 decimals of precision.
|
|
428
|
+
* @return tokenAmount Equivalent amount in token native decimals.
|
|
406
429
|
*/
|
|
430
|
+
|
|
407
431
|
function fromUSD(
|
|
408
432
|
address token,
|
|
409
433
|
uint256 usdAmount
|
|
410
434
|
) external view returns (uint256 tokenAmount) {
|
|
411
435
|
if (usdAmount == 0) return 0;
|
|
412
|
-
uint8 decimals =
|
|
436
|
+
uint8 decimals = tokenDecimals[token];
|
|
413
437
|
uint256 price = _getPrice(token); // 18 decimals
|
|
414
438
|
uint256 normalized = (usdAmount * UBKOracleConstants.WAD) / price;
|
|
415
439
|
tokenAmount = (normalized * (10 ** decimals)) / UBKOracleConstants.WAD;
|
|
@@ -420,14 +444,35 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
420
444
|
// -----------------------------------------------------------------------
|
|
421
445
|
|
|
422
446
|
/**
|
|
423
|
-
* @notice Returns the cached price for a token
|
|
424
|
-
*
|
|
447
|
+
* @notice Returns the cached USD price for a token.
|
|
448
|
+
*
|
|
449
|
+
* @dev
|
|
450
|
+
* This function is the canonical read path for all pricing operations
|
|
451
|
+
* (including `toUSD` and `fromUSD`). It intentionally performs **no explicit
|
|
452
|
+
* supported-token check** in order to minimize gas usage on hot, view-only paths.
|
|
453
|
+
*
|
|
454
|
+
* Reverts in the following cases:
|
|
455
|
+
*
|
|
456
|
+
* 1. `NoFallbackPrice(token)`
|
|
457
|
+
* - The token has **never had a successfully resolved and cached price**, OR
|
|
458
|
+
* - The token is **unsupported** and therefore has no initialized pricing state.
|
|
459
|
+
*
|
|
460
|
+
* 2. `StalePrice(token, lastUpdated, now)`
|
|
461
|
+
* - A cached price exists, but its age exceeds `stalePeriod[token]`.
|
|
462
|
+
*
|
|
463
|
+
* Supported tokens are guaranteed to have:
|
|
464
|
+
* - non-zero addresses,
|
|
465
|
+
* - validated and cached decimals within [6, 18],
|
|
466
|
+
* - pricing state initialized only via explicit configuration paths
|
|
467
|
+
* (e.g. Chainlink feeds, ERC4626 vaults, or manual pricing).
|
|
468
|
+
*
|
|
469
|
+
* As a result, callers of `toUSD` and `fromUSD` can safely rely on this function
|
|
470
|
+
* for correctness while avoiding redundant SLOADs or branching.
|
|
471
|
+
*
|
|
425
472
|
* @param token Asset token address.
|
|
426
|
-
* @return price Cached price in 1e18 precision.
|
|
473
|
+
* @return price Cached token price in 1e18 precision.
|
|
427
474
|
*/
|
|
428
|
-
function _getPrice(address token) internal view returns (uint256) {
|
|
429
|
-
if (token == address(0))
|
|
430
|
-
revert ZeroAddress("UBKOracle::getPrice", "token");
|
|
475
|
+
function _getPrice(address token) internal view returns (uint256 price) {
|
|
431
476
|
LastValidPrice memory lv = lastValidPrice[token];
|
|
432
477
|
if (lv.price == 0) revert NoFallbackPrice(token);
|
|
433
478
|
if (!_isPriceFresh(token))
|
|
@@ -454,17 +499,20 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
454
499
|
* @dev Derives price via convertToAssets() * underlying price.
|
|
455
500
|
* Ensures vault rate lies within acceptable bounds.
|
|
456
501
|
*/
|
|
457
|
-
function
|
|
502
|
+
function _resolveVaultPrice(
|
|
458
503
|
address vault,
|
|
459
504
|
address underlying
|
|
460
505
|
) internal checkRecursion returns (uint256 price) {
|
|
461
|
-
uint8 shareDecimals =
|
|
462
|
-
uint8 underlyingDecimals =
|
|
506
|
+
uint8 shareDecimals = tokenDecimals[vault];
|
|
507
|
+
uint8 underlyingDecimals = tokenDecimals[underlying];
|
|
463
508
|
|
|
464
509
|
uint256 oneShare = 10 ** shareDecimals;
|
|
465
510
|
uint256 assetsPerShare = IERC4626(vault).convertToAssets(oneShare);
|
|
466
|
-
if (
|
|
467
|
-
|
|
511
|
+
if (
|
|
512
|
+
assetsPerShare == 0 ||
|
|
513
|
+
assetsPerShare >
|
|
514
|
+
UBKOracleConstants.ORACLE_MAX_VAULT_ASSETS_PER_SHARE
|
|
515
|
+
) revert InvalidVaultExchangeRate(vault, assetsPerShare);
|
|
468
516
|
|
|
469
517
|
uint256 scaledAssets = (assetsPerShare * UBKOracleConstants.WAD) /
|
|
470
518
|
(10 ** underlyingDecimals);
|
|
@@ -549,11 +597,14 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
549
597
|
* @return price Resolved fair price in 1e18 precision.
|
|
550
598
|
* @dev May trigger state updates if underlying vaults are resolved.
|
|
551
599
|
*/
|
|
552
|
-
function _resolvePrice(
|
|
600
|
+
function _resolvePrice(
|
|
601
|
+
address token
|
|
602
|
+
) internal supportedTokenOnly(token) returns (uint256 price) {
|
|
553
603
|
if (isManual[token]) return manualPrices[token];
|
|
554
604
|
|
|
555
605
|
address underlying = erc4626Underlying[token];
|
|
556
|
-
if (underlying != address(0))
|
|
606
|
+
if (underlying != address(0))
|
|
607
|
+
return _resolveVaultPrice(token, underlying);
|
|
557
608
|
|
|
558
609
|
address feed = chainlinkFeeds[token];
|
|
559
610
|
if (feed == address(0)) revert NoPriceFeed(token);
|
|
@@ -588,7 +639,7 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
588
639
|
if (
|
|
589
640
|
price < UBKOracleConstants.ORACLE_MIN_ABSOLUTE_PRICE_WAD ||
|
|
590
641
|
price > UBKOracleConstants.ORACLE_MAX_ABSOLUTE_PRICE_WAD
|
|
591
|
-
) revert InvalidOraclePrice(token,
|
|
642
|
+
) revert InvalidOraclePrice(token, price);
|
|
592
643
|
|
|
593
644
|
lastValidPrice[token] = LastValidPrice(price, block.timestamp);
|
|
594
645
|
emit LastValidPriceUpdated(token, price, block.timestamp);
|
|
@@ -606,10 +657,85 @@ contract UBKOracle is IUBKOracle, Ownable {
|
|
|
606
657
|
* Emits a {TokenSupportAdded} event upon successful addition.
|
|
607
658
|
*/
|
|
608
659
|
function _addSupportedToken(address token) internal {
|
|
660
|
+
if (token == address(0)) {
|
|
661
|
+
revert ZeroAddress("UBKOracle::_addSupportedToken", "token");
|
|
662
|
+
}
|
|
663
|
+
|
|
609
664
|
if (!isSupported[token]) {
|
|
610
665
|
supportedTokens.push(token);
|
|
611
666
|
isSupported[token] = true;
|
|
667
|
+
tokenDecimals[token] = _validateTokenDecimals(token);
|
|
668
|
+
_setStalePeriod(
|
|
669
|
+
token,
|
|
670
|
+
UBKOracleConstants.ORACLE_DEFAULT_STALE_PERIOD
|
|
671
|
+
);
|
|
612
672
|
emit TokenSupportAdded(token);
|
|
613
673
|
}
|
|
614
674
|
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* @notice Validates assets and their associated Chainlink feeds before mutating system state.
|
|
678
|
+
* @param token Asset token address.
|
|
679
|
+
* @param feed Chainlink AggregatorV3 feed address.
|
|
680
|
+
* @dev Ensures decimals ≤ 18 and feed returns a nonzero updatedAt value.
|
|
681
|
+
*/
|
|
682
|
+
function _validateChainlinkFeed(address token, address feed) internal view {
|
|
683
|
+
if (token == address(0))
|
|
684
|
+
revert ZeroAddress("UBKOracle::setChainlinkFeed", "token");
|
|
685
|
+
if (feed == address(0))
|
|
686
|
+
revert ZeroAddress("UBKOracle::setChainlinkFeed", "feed");
|
|
687
|
+
if (feed.code.length == 0) revert InvalidFeedContract(feed);
|
|
688
|
+
_validateTokenDecimals(token);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* @notice Validates input parameters before setting an ERc 4626 vault.
|
|
693
|
+
* @dev Ensures decimals for vault and underlying assets are equal, and bounded by global invariants.
|
|
694
|
+
* @param vault ERC-4626 asset.
|
|
695
|
+
* @param underlying ERC-20 asset.
|
|
696
|
+
*/
|
|
697
|
+
function _validateERC4626Vault(
|
|
698
|
+
address vault,
|
|
699
|
+
address underlying
|
|
700
|
+
) internal view {
|
|
701
|
+
if (vault == address(0))
|
|
702
|
+
revert ZeroAddress("UBKOracle::_validateERC4626Vault", "vault");
|
|
703
|
+
if (underlying == address(0)) {
|
|
704
|
+
revert ZeroAddress(
|
|
705
|
+
"UBKOracle::_validateERC4626Vault",
|
|
706
|
+
"underlying"
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
try IERC4626(vault).asset() returns (address) {} catch {
|
|
710
|
+
revert InvalidERC4626Vault(vault);
|
|
711
|
+
}
|
|
712
|
+
uint8 vaultDecimals = _validateTokenDecimals(vault); // Must be between [6,18]
|
|
713
|
+
uint8 underlyingDecimals = _validateTokenDecimals(underlying); // Must be between [6,18]
|
|
714
|
+
|
|
715
|
+
if (vaultDecimals != underlyingDecimals) {
|
|
716
|
+
revert ERC4626DecimalsMismatch(
|
|
717
|
+
"UBKOracle::_validateERC4626Vault",
|
|
718
|
+
vault,
|
|
719
|
+
underlying
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* @notice Sets the maximum time (in seconds) a Chainlink feed value is valid.
|
|
726
|
+
* @param period The new fallback staleness threshold. Must be 1.5x of stalePeriod of the same token.
|
|
727
|
+
* @dev Must lie within [UBKOracleConstants.ORACLE_MIN_STALE_PERIOD, UBKOracleConstants.ORACLE_MAX_STALE_PERIOD].
|
|
728
|
+
*/
|
|
729
|
+
function _setStalePeriod(address token, uint256 period) internal {
|
|
730
|
+
if (
|
|
731
|
+
period < UBKOracleConstants.ORACLE_MIN_STALE_PERIOD ||
|
|
732
|
+
period > UBKOracleConstants.ORACLE_MAX_STALE_PERIOD
|
|
733
|
+
) revert InvalidStalePeriod(period);
|
|
734
|
+
stalePeriod[token] = period;
|
|
735
|
+
fallbackStalePeriod[token] =
|
|
736
|
+
UBKOracleConstants.ORACLE_DEFAULT_STALE_FALLBACK_MULTIPLIER *
|
|
737
|
+
period; //Minimum fallback period should be 2x stalePeriod[token].
|
|
738
|
+
emit StalePeriodUpdated(token, stalePeriod[token]);
|
|
739
|
+
emit FallbackStalePeriodUpdated(token, fallbackStalePeriod[token]);
|
|
740
|
+
}
|
|
615
741
|
}
|
|
@@ -4,18 +4,24 @@ pragma solidity ^0.8.20;
|
|
|
4
4
|
import "@ubk-labs/ubk-commons/contracts/errors/UBKErrors.sol";
|
|
5
5
|
|
|
6
6
|
// ───────────── Errors ─────────────
|
|
7
|
+
error ERC4626DecimalsMismatch(
|
|
8
|
+
string functionName,
|
|
9
|
+
address vault,
|
|
10
|
+
address underlying
|
|
11
|
+
);
|
|
12
|
+
error InvalidERC4626Vault(address vault);
|
|
13
|
+
error InvalidFeedContract(address feed);
|
|
14
|
+
error InvalidFeedDecimals(address feed, uint8 decimals);
|
|
7
15
|
error InvalidManualPrice(address token, uint256 price);
|
|
16
|
+
error InvalidOraclePrice(address token, uint256 price);
|
|
8
17
|
error InvalidStalePeriod(uint256 period);
|
|
9
18
|
error InvalidVaultBounds(address vault, uint256 minRate, uint256 maxRate);
|
|
10
|
-
error InvalidFeedContract(address feed);
|
|
11
|
-
error InvalidFeedDecimals(address feed, uint8 decimals);
|
|
12
|
-
error InvalidERC4626Vault(address vault);
|
|
13
19
|
error InvalidVaultExchangeRate(address vault, uint256 rate);
|
|
14
|
-
error
|
|
20
|
+
error NoFallbackPrice(address token);
|
|
15
21
|
error NoPriceFeed(address token);
|
|
16
|
-
error
|
|
22
|
+
error OraclePaused(address oracle, uint256 timestamp);
|
|
23
|
+
error RecursiveResolution(address token);
|
|
17
24
|
error StaleFallback(address token);
|
|
18
|
-
error
|
|
25
|
+
error StalePrice(address token, uint256 updatedAt, uint256 currentTime);
|
|
19
26
|
error SuspiciousVaultRate(address vault, uint256 rate);
|
|
20
|
-
error
|
|
21
|
-
error OraclePaused(address oracle, uint256 timestamp);
|
|
27
|
+
error TokenNotSupported(address token);
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubk-labs/ubk-oracle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Oracle supporting ERC-20 and ERC-4626 assets for use in decentralized financial applications.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "npx hardhat compile",
|
|
7
7
|
"test": "npx hardhat test",
|
|
8
8
|
"coverage": "hardhat coverage",
|
|
9
9
|
"lint": "prettier --check .",
|
|
10
|
-
"format": "prettier --write ."
|
|
10
|
+
"format": "prettier --write .",
|
|
11
|
+
"deploy:mainnet": "npx hardhat run scripts/deploy.js --network mainnet"
|
|
11
12
|
},
|
|
12
13
|
"keywords": [
|
|
13
14
|
"DeFi",
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
"files": [
|
|
21
22
|
"contracts",
|
|
22
23
|
"interfaces",
|
|
24
|
+
"deployments",
|
|
23
25
|
"README.md",
|
|
24
26
|
"LICENSE",
|
|
25
27
|
"package.json"
|
|
@@ -37,6 +39,6 @@
|
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
41
|
"@chainlink/contracts": "^0.6.1",
|
|
40
|
-
"@ubk-labs/ubk-commons": "^0.1.
|
|
42
|
+
"@ubk-labs/ubk-commons": "^0.1.7"
|
|
41
43
|
}
|
|
42
44
|
}
|