// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "./interfaces/IChainlinkPriceFeed.sol"; /// @title ExchangeRateOracle — 汇率预言机集成 /// @notice 集成 Chainlink Price Feed,提供各币种对 USD 汇率 /// @dev 支持多币种,内置过期保护(最多15分钟) contract ExchangeRateOracle is Initializable, AccessControlUpgradeable { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); // 币种 → Chainlink Price Feed 地址 mapping(string => address) public priceFeeds; // 支持的币种列表 string[] public supportedCurrencies; // 汇率数据最大容许延迟(秒) uint256 public maxStaleness; // --- Events --- event PriceFeedSet(string indexed currency, address feed); event PriceFeedRemoved(string indexed currency); event MaxStalenessUpdated(uint256 oldValue, uint256 newValue); function initialize(uint256 _maxStaleness, address admin) external initializer { __AccessControl_init(); maxStaleness = _maxStaleness > 0 ? _maxStaleness : 900; // 默认 15 分钟 _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(ADMIN_ROLE, admin); } /// @notice 获取某币种对 USD 的汇率 /// @param currency 币种标识(如 "CNY", "JPY", "EUR") /// @return rate 汇率(Chainlink 精度,通常 8 位小数) function getRate(string calldata currency) external view returns (uint256 rate) { address feed = priceFeeds[currency]; require(feed != address(0), "Oracle: unsupported currency"); (, int256 answer,, uint256 updatedAt,) = IChainlinkPriceFeed(feed).latestRoundData(); require(answer > 0, "Oracle: invalid price"); require(block.timestamp - updatedAt <= maxStaleness, "Oracle: stale price data"); rate = uint256(answer); } /// @notice 获取汇率及详细信息 function getRateDetails(string calldata currency) external view returns (uint256 rate, uint256 updatedAt, uint8 decimals) { address feed = priceFeeds[currency]; require(feed != address(0), "Oracle: unsupported currency"); (, int256 answer,, uint256 _updatedAt,) = IChainlinkPriceFeed(feed).latestRoundData(); require(answer > 0, "Oracle: invalid price"); rate = uint256(answer); updatedAt = _updatedAt; decimals = IChainlinkPriceFeed(feed).decimals(); } /// @notice 设置币种的 Price Feed 地址 function setPriceFeed(string calldata currency, address feed) external onlyRole(ADMIN_ROLE) { require(feed != address(0), "Oracle: zero address"); require(bytes(currency).length > 0, "Oracle: empty currency"); // 新增还是更新 if (priceFeeds[currency] == address(0)) { supportedCurrencies.push(currency); } priceFeeds[currency] = feed; emit PriceFeedSet(currency, feed); } /// @notice 移除币种 Price Feed function removePriceFeed(string calldata currency) external onlyRole(ADMIN_ROLE) { require(priceFeeds[currency] != address(0), "Oracle: feed not found"); priceFeeds[currency] = address(0); emit PriceFeedRemoved(currency); } /// @notice 更新最大过期时间 function setMaxStaleness(uint256 _maxStaleness) external onlyRole(ADMIN_ROLE) { require(_maxStaleness > 0, "Oracle: zero staleness"); uint256 old = maxStaleness; maxStaleness = _maxStaleness; emit MaxStalenessUpdated(old, _maxStaleness); } /// @notice 获取支持的币种数量 function supportedCurrencyCount() external view returns (uint256) { return supportedCurrencies.length; } }