@ubk-labs/ubk-oracle 0.2.0 → 0.2.1
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.
|
@@ -39,4 +39,10 @@ library UBKOracleConstants {
|
|
|
39
39
|
// Oracle Recursion
|
|
40
40
|
// -----------------------------------------------------------------------
|
|
41
41
|
uint256 public constant ORACLE_MAX_RECURSION_DEPTH = 5;
|
|
42
|
+
|
|
43
|
+
// -----------------------------------------------------------------------
|
|
44
|
+
// Chainlink Feed Decimals Bounds
|
|
45
|
+
// -----------------------------------------------------------------------
|
|
46
|
+
uint256 public constant ORACLE_MIN_CHAINLINK_FEED_DECIMALS = UBKConstants.GLOBAL_MIN_TOKEN_DECIMALS_ALLOWED; // Same as UBKDecimalsBounded (6)
|
|
47
|
+
uint256 public constant ORACLE_MAX_CHAINLINK_FEED_DECIMALS = UBKConstants.GLOBAL_MAX_TOKEN_DECIMALS_ALLOWED; // Same as UBKDecimalsBounded (18)
|
|
42
48
|
}
|
|
@@ -88,7 +88,10 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
88
88
|
mapping(address => bool) public isSupported;
|
|
89
89
|
|
|
90
90
|
/// @notice Cache that tracks decimals for supported tokens.
|
|
91
|
-
mapping(address => uint8) internal
|
|
91
|
+
mapping(address => uint8) internal tokenDecimalsCache;
|
|
92
|
+
|
|
93
|
+
/// @notice Cache that tracks decimals for registered Chainlink aggregators.
|
|
94
|
+
mapping(address => uint8) internal aggDecimalsCache;
|
|
92
95
|
|
|
93
96
|
// -----------------------------------------------------------------------
|
|
94
97
|
// Constructor & Modifiers
|
|
@@ -245,18 +248,44 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
245
248
|
}
|
|
246
249
|
|
|
247
250
|
/**
|
|
248
|
-
* @notice Registers a Chainlink feed
|
|
249
|
-
*
|
|
250
|
-
* @
|
|
251
|
-
*
|
|
251
|
+
* @notice Registers a Chainlink price feed as the canonical oracle source for a token.
|
|
252
|
+
*
|
|
253
|
+
* @dev
|
|
254
|
+
* This function performs all required validation before binding a token to a
|
|
255
|
+
* Chainlink AggregatorV3 feed. If this call succeeds, the oracle guarantees that:
|
|
256
|
+
*
|
|
257
|
+
* - `token` is explicitly supported by the oracle and has validated, cached
|
|
258
|
+
* ERC-20 decimals within global bounds.
|
|
259
|
+
* - `feed` is a deployed AggregatorV3 contract with bounded, immutable decimals
|
|
260
|
+
* that are safe to cache and use for price normalization.
|
|
261
|
+
* - The feed is live and returning sane data at registration time
|
|
262
|
+
* (non-zero answer and timestamp).
|
|
263
|
+
*
|
|
264
|
+
* Upon successful registration:
|
|
265
|
+
* - The feed address and its decimals are cached immutably.
|
|
266
|
+
* - Manual pricing for `token` is disabled.
|
|
267
|
+
* - The token becomes eligible for pricing, conversion, and fallback logic
|
|
268
|
+
* across all oracle read paths.
|
|
269
|
+
*
|
|
270
|
+
* This function is administrative and MUST be called only during trusted
|
|
271
|
+
* configuration or governance actions. Runtime pricing paths assume that all
|
|
272
|
+
* invariants enforced here permanently hold.
|
|
273
|
+
*
|
|
274
|
+
* @param token The asset token whose price will be sourced from Chainlink.
|
|
275
|
+
* @param feed The Chainlink AggregatorV3 contract providing price data for `token`.
|
|
276
|
+
*
|
|
277
|
+
* @custom:invariant If this function returns, the oracle may safely resolve,
|
|
278
|
+
* normalize, and cache prices for `token` without performing
|
|
279
|
+
* any further structural validation on the feed.
|
|
252
280
|
*/
|
|
253
281
|
function setChainlinkFeed(address token, address feed) external onlyOwner {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
282
|
+
// 1. Validate inputs and extract trusted feed metadata
|
|
283
|
+
(AggregatorV3Interface agg, uint8 aggDecimals) = _validateChainlinkFeed(
|
|
284
|
+
token,
|
|
285
|
+
feed
|
|
286
|
+
);
|
|
259
287
|
|
|
288
|
+
// 2. Ensure the feed is live and returning sane data
|
|
260
289
|
try agg.latestRoundData() returns (
|
|
261
290
|
uint80,
|
|
262
291
|
int256 answer,
|
|
@@ -268,9 +297,13 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
268
297
|
} catch {
|
|
269
298
|
revert InvalidFeedContract(feed);
|
|
270
299
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
300
|
+
// 3. Commit validated configuration to storage
|
|
301
|
+
chainlinkFeeds[token] = feed; // Map token to feed
|
|
302
|
+
aggDecimalsCache[feed] = aggDecimals; // Cache feed decimals
|
|
303
|
+
isManual[token] = false; // Set manual mode to false
|
|
304
|
+
_addSupportedToken(token); // Register support for token and cache token decimals.
|
|
305
|
+
|
|
306
|
+
// Emit event.
|
|
274
307
|
emit ChainlinkFeedSet(token, feed);
|
|
275
308
|
}
|
|
276
309
|
|
|
@@ -404,7 +437,7 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
404
437
|
uint256 amount
|
|
405
438
|
) external view returns (uint256 usdValue) {
|
|
406
439
|
if (amount == 0) return 0;
|
|
407
|
-
uint8 decimals =
|
|
440
|
+
uint8 decimals = tokenDecimalsCache[token];
|
|
408
441
|
uint256 normalized = (amount * UBKOracleConstants.WAD) /
|
|
409
442
|
(10 ** decimals);
|
|
410
443
|
uint256 price = _getPrice(token); // 18 decimals
|
|
@@ -433,7 +466,7 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
433
466
|
uint256 usdAmount
|
|
434
467
|
) external view returns (uint256 tokenAmount) {
|
|
435
468
|
if (usdAmount == 0) return 0;
|
|
436
|
-
uint8 decimals =
|
|
469
|
+
uint8 decimals = tokenDecimalsCache[token];
|
|
437
470
|
uint256 price = _getPrice(token); // 18 decimals
|
|
438
471
|
uint256 normalized = (usdAmount * UBKOracleConstants.WAD) / price;
|
|
439
472
|
tokenAmount = (normalized * (10 ** decimals)) / UBKOracleConstants.WAD;
|
|
@@ -503,8 +536,8 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
503
536
|
address vault,
|
|
504
537
|
address underlying
|
|
505
538
|
) internal checkRecursion returns (uint256 price) {
|
|
506
|
-
uint8 shareDecimals =
|
|
507
|
-
uint8 underlyingDecimals =
|
|
539
|
+
uint8 shareDecimals = tokenDecimalsCache[vault];
|
|
540
|
+
uint8 underlyingDecimals = tokenDecimalsCache[underlying];
|
|
508
541
|
|
|
509
542
|
uint256 oneShare = 10 ** shareDecimals;
|
|
510
543
|
uint256 assetsPerShare = IERC4626(vault).convertToAssets(oneShare);
|
|
@@ -566,7 +599,7 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
566
599
|
block.timestamp - updatedAt > stalePeriod[token]
|
|
567
600
|
) return (0, false);
|
|
568
601
|
|
|
569
|
-
uint8 feedDecimals =
|
|
602
|
+
uint8 feedDecimals = aggDecimalsCache[feed];
|
|
570
603
|
uint256 raw = uint256(answer);
|
|
571
604
|
|
|
572
605
|
uint256 clPrice;
|
|
@@ -664,7 +697,7 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
664
697
|
if (!isSupported[token]) {
|
|
665
698
|
supportedTokens.push(token);
|
|
666
699
|
isSupported[token] = true;
|
|
667
|
-
|
|
700
|
+
tokenDecimalsCache[token] = _validateTokenDecimals(token);
|
|
668
701
|
_setStalePeriod(
|
|
669
702
|
token,
|
|
670
703
|
UBKOracleConstants.ORACLE_DEFAULT_STALE_PERIOD
|
|
@@ -674,18 +707,59 @@ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
|
|
|
674
707
|
}
|
|
675
708
|
|
|
676
709
|
/**
|
|
677
|
-
* @notice Validates
|
|
678
|
-
*
|
|
679
|
-
* @
|
|
680
|
-
*
|
|
710
|
+
* @notice Validates and canonicalizes a Chainlink price feed configuration.
|
|
711
|
+
*
|
|
712
|
+
* @dev
|
|
713
|
+
* This function performs all one-time validation required before a Chainlink
|
|
714
|
+
* aggregator may be trusted by the oracle. If this function returns successfully,
|
|
715
|
+
* the following invariants are guaranteed for the lifetime of the configuration:
|
|
716
|
+
*
|
|
717
|
+
* - `token` is a non-zero ERC-20 address with validated decimals within
|
|
718
|
+
* the oracle’s global bounds.
|
|
719
|
+
* - `feed` is a non-zero deployed contract address implementing the
|
|
720
|
+
* Chainlink AggregatorV3 interface.
|
|
721
|
+
* - The aggregator’s reported decimals lie within the oracle’s accepted
|
|
722
|
+
* Chainlink decimal range and are safe to cache immutably.
|
|
723
|
+
*
|
|
724
|
+
* This function does NOT:
|
|
725
|
+
* - read or validate live pricing data,
|
|
726
|
+
* - mutate oracle state,
|
|
727
|
+
* - assume any particular price semantics beyond scale correctness.
|
|
728
|
+
*
|
|
729
|
+
* It exists to separate structural validation from runtime pricing logic,
|
|
730
|
+
* enabling deterministic gas usage and invariant-based reasoning throughout
|
|
731
|
+
* the oracle’s hot paths.
|
|
732
|
+
*
|
|
733
|
+
* @param token The asset token whose price will be sourced from the feed.
|
|
734
|
+
* @param feed The Chainlink AggregatorV3 contract providing price data for `token`.
|
|
735
|
+
*
|
|
736
|
+
* @return agg The validated AggregatorV3Interface instance.
|
|
737
|
+
* @return aggDecimals The validated and bounded number of decimals used by the aggregator.
|
|
738
|
+
*
|
|
739
|
+
* @custom:invariant If this function returns, `aggDecimals` may be safely cached
|
|
740
|
+
* and used for all future price normalization without further
|
|
741
|
+
* external calls.
|
|
681
742
|
*/
|
|
682
|
-
function _validateChainlinkFeed(
|
|
743
|
+
function _validateChainlinkFeed(
|
|
744
|
+
address token,
|
|
745
|
+
address feed
|
|
746
|
+
) internal view returns (AggregatorV3Interface agg, uint8 aggDecimals) {
|
|
683
747
|
if (token == address(0))
|
|
684
|
-
revert ZeroAddress("UBKOracle::
|
|
748
|
+
revert ZeroAddress("UBKOracle::_validateChainlinkFeed", "token");
|
|
685
749
|
if (feed == address(0))
|
|
686
|
-
revert ZeroAddress("UBKOracle::
|
|
750
|
+
revert ZeroAddress("UBKOracle::_validateChainlinkFeed", "feed");
|
|
687
751
|
if (feed.code.length == 0) revert InvalidFeedContract(feed);
|
|
688
|
-
|
|
752
|
+
|
|
753
|
+
_validateTokenDecimals(token); // Ensure ERC-20 tokens are in range.
|
|
754
|
+
|
|
755
|
+
agg = AggregatorV3Interface(feed);
|
|
756
|
+
aggDecimals = agg.decimals();
|
|
757
|
+
|
|
758
|
+
if (
|
|
759
|
+
aggDecimals <
|
|
760
|
+
UBKOracleConstants.ORACLE_MIN_CHAINLINK_FEED_DECIMALS ||
|
|
761
|
+
aggDecimals > UBKOracleConstants.ORACLE_MAX_CHAINLINK_FEED_DECIMALS
|
|
762
|
+
) revert InvalidFeedDecimals(feed, aggDecimals); // Ensure aggregator decimals are in range.
|
|
689
763
|
}
|
|
690
764
|
|
|
691
765
|
/**
|
package/package.json
CHANGED