@ubk-labs/ubk-oracle 0.1.7 → 0.1.9

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.
@@ -7,6 +7,7 @@ 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";
@@ -39,7 +40,7 @@ import "../constants/UBKOracleConstants.sol";
39
40
  * - UI / Subgraphs can use `isPriceFresh()` and `getPriceAge()` for safety checks.
40
41
  *
41
42
  */
42
- contract UBKOracle is IUBKOracle, Ownable {
43
+ contract UBKOracle is IUBKOracle, UBKDecimalsBounded, Ownable {
43
44
  // -----------------------------------------------------------------------
44
45
  // Storage
45
46
  // -----------------------------------------------------------------------
@@ -87,10 +88,7 @@ contract UBKOracle is IUBKOracle, Ownable {
87
88
  * @notice Deploys the Oracle contract.
88
89
  * @param _owner The address to assign as the owner (governance or deployer).
89
90
  */
90
- constructor(address _owner) Ownable(_owner) {
91
- if (_owner == address(0))
92
- revert ZeroAddress("UBKOracle::constructor", "owner");
93
- }
91
+ constructor(address _owner) Ownable(_owner) {}
94
92
 
95
93
  /// @notice Ensures oracle is not paused.
96
94
  modifier whenNotPaused() {
@@ -240,9 +238,7 @@ contract UBKOracle is IUBKOracle, Ownable {
240
238
  * @dev Ensures decimals ≤ 18 and feed returns a nonzero updatedAt value.
241
239
  */
242
240
  function setChainlinkFeed(address token, address feed) external onlyOwner {
243
- if (token == address(0) || feed == address(0))
244
- revert ZeroAddress("UBKOracle::setChainlinkFeed", "input");
245
- if (feed.code.length == 0) revert InvalidFeedContract(feed);
241
+ _validateChainlinkFeed(token, feed);
246
242
 
247
243
  AggregatorV3Interface agg = AggregatorV3Interface(feed);
248
244
  uint8 decimals = agg.decimals();
@@ -276,11 +272,7 @@ contract UBKOracle is IUBKOracle, Ownable {
276
272
  address vault,
277
273
  address underlying
278
274
  ) external onlyOwner {
279
- if (vault == address(0) || underlying == address(0))
280
- revert ZeroAddress("UBKOracle::setERC4626Vault", "input");
281
- try IERC4626(vault).asset() returns (address) {} catch {
282
- revert InvalidERC4626Vault(vault);
283
- }
275
+ _validateERC4626Vault(vault, underlying);
284
276
  erc4626Underlying[vault] = underlying;
285
277
  _addSupportedToken(vault);
286
278
  emit ERC4626Registered(vault, underlying);
@@ -312,6 +304,36 @@ contract UBKOracle is IUBKOracle, Ownable {
312
304
  return _fetchAndUpdatePrice(token);
313
305
  }
314
306
 
307
+ /**
308
+ * @notice Batch price fetch & update for multiple tokens.
309
+ * @param tokens Array of asset token addresses.
310
+ * @return prices Array of fresh prices in 1e18 precision.
311
+ *
312
+ * @dev
313
+ * - Runs whenNotPaused modifier.
314
+ * - Reverts if any token is zero address.
315
+ * - Each iteration calls the same internal `_fetchAndUpdatePrice`.
316
+ * - Gas-efficient: msg.sender checked once, calldata parsed once.
317
+ */
318
+ function fetchAndUpdatePrice(
319
+ address[] calldata tokens
320
+ ) external whenNotPaused returns (uint256[] memory prices) {
321
+ uint256 len = tokens.length;
322
+ prices = new uint256[](len);
323
+
324
+ for (uint256 i = 0; i < len; ++i) {
325
+ address token = tokens[i];
326
+ if (token == address(0)) {
327
+ revert ZeroAddress(
328
+ "UBKOracle::fetchAndUpdatePriceBatch",
329
+ "token"
330
+ );
331
+ }
332
+
333
+ prices[i] = _fetchAndUpdatePrice(token);
334
+ }
335
+ }
336
+
315
337
  /**
316
338
  * @notice Returns age of last cached price in seconds.
317
339
  * @param token Token address.
@@ -415,7 +437,7 @@ contract UBKOracle is IUBKOracle, Ownable {
415
437
  return (lv.timestamp != 0 &&
416
438
  block.timestamp - lv.timestamp <= stalePeriod[token]);
417
439
  }
418
-
440
+
419
441
  /**
420
442
  * @notice Resolves the fair price of an ERC4626 vault share.
421
443
  * @param vault ERC4626 vault token address.
@@ -582,4 +604,52 @@ contract UBKOracle is IUBKOracle, Ownable {
582
604
  emit TokenSupportAdded(token);
583
605
  }
584
606
  }
607
+
608
+ /**
609
+ * @notice Validates assets and their associated Chainlink feeds before mutating system state.
610
+ * @param token Asset token address.
611
+ * @param feed Chainlink AggregatorV3 feed address.
612
+ * @dev Ensures decimals ≤ 18 and feed returns a nonzero updatedAt value.
613
+ */
614
+ function _validateChainlinkFeed(address token, address feed) internal view {
615
+ if (token == address(0))
616
+ revert ZeroAddress("UBKOracle::setChainlinkFeed", "token");
617
+ if (feed == address(0))
618
+ revert ZeroAddress("UBKOracle::setChainlinkFeed", "feed");
619
+ if (feed.code.length == 0) revert InvalidFeedContract(feed);
620
+ _validateTokenDecimals(token);
621
+ }
622
+
623
+ /**
624
+ * @notice Validates input parameters before setting an ERc 4626 vault.
625
+ * @dev Ensures decimals for vault and underlying assets are equal, and bounded by global invariants.
626
+ * @param vault ERC-4626 asset.
627
+ * @param underlying ERC-20 asset.
628
+ */
629
+ function _validateERC4626Vault(
630
+ address vault,
631
+ address underlying
632
+ ) internal view {
633
+ if (vault == address(0))
634
+ revert ZeroAddress("UBKOracle::_validateERC4626Vault", "vault");
635
+ if (underlying == address(0)) {
636
+ revert ZeroAddress(
637
+ "UBKOracle::_validateERC4626Vault",
638
+ "underlying"
639
+ );
640
+ }
641
+ try IERC4626(vault).asset() returns (address) {} catch {
642
+ revert InvalidERC4626Vault(vault);
643
+ }
644
+ uint8 vaultDecimals = _validateTokenDecimals(vault); // Must be between [6,18]
645
+ uint8 underlyingDecimals = _validateTokenDecimals(underlying); // Must be between [6,18]
646
+
647
+ if (vaultDecimals != underlyingDecimals) {
648
+ revert ERC4626DecimalsMismatch(
649
+ "UBKOracle::_validateERC4626Vault",
650
+ vault,
651
+ underlying
652
+ );
653
+ }
654
+ }
585
655
  }
@@ -19,3 +19,4 @@ error NoFallbackPrice(address token);
19
19
  error SuspiciousVaultRate(address vault, uint256 rate);
20
20
  error RecursiveResolution(address token);
21
21
  error OraclePaused(address oracle, uint256 timestamp);
22
+ error ERC4626DecimalsMismatch(string functionName, address vault, address underlying);
@@ -0,0 +1,5 @@
1
+ {
2
+ "0.1.7": "0x37814FAA5bd659888380CBa070F098Cc0999A8cA",
3
+ "0.1.8": "0x5d4747d514B529005F4014f26068E1f4Ec47E06B",
4
+ "0.1.9": "0x04B21e27e24Ab71472690C9a09BC7A826d658668"
5
+ }
@@ -94,7 +94,9 @@ interface IUBKOracle {
94
94
  * @dev Keeper entrypoint. May revert if price resolution fails.
95
95
  */
96
96
  function fetchAndUpdatePrice(address token) external returns (uint256);
97
-
97
+ function fetchAndUpdatePrice(
98
+ address[] calldata tokens
99
+ ) external returns (uint256[] memory);
98
100
  // -----------------------------------------------------------------------
99
101
  // ADMIN / GOVERNANCE CONFIGURATION
100
102
  // -----------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubk-labs/ubk-oracle",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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",
@@ -20,6 +20,7 @@
20
20
  "files": [
21
21
  "contracts",
22
22
  "interfaces",
23
+ "deployments",
23
24
  "README.md",
24
25
  "LICENSE",
25
26
  "package.json"
@@ -37,6 +38,6 @@
37
38
  },
38
39
  "dependencies": {
39
40
  "@chainlink/contracts": "^0.6.1",
40
- "@ubk-labs/ubk-commons": "^0.1.5"
41
+ "@ubk-labs/ubk-commons": "^0.1.7"
41
42
  }
42
43
  }