// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/IOracleAdapter.sol"; /** * @title DBISOracleAdapter * @notice Standardizes pricing from multiple oracle sources * @dev Aggregates prices from Aave, Chainlink, Uniswap TWAP with confidence scoring */ contract DBISOracleAdapter is IOracleAdapter, Ownable { // Constants uint256 private constant PRICE_SCALE = 1e8; uint256 private constant CONFIDENCE_SCALE = 1e18; uint256 private constant MAX_PRICE_AGE = 1 hours; // Chainlink price feed interface (simplified) interface AggregatorV3Interface { function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); function decimals() external view returns (uint8); } // Aave Oracle interface (simplified) interface IAaveOracle { function getAssetPrice(address asset) external view returns (uint256); } // Asset configuration struct AssetConfig { address chainlinkFeed; address aaveOracle; address uniswapPool; bool enabled; uint256 chainlinkWeight; // Weight for price aggregation (out of 1e18) uint256 aaveWeight; uint256 uniswapWeight; } mapping(address => AssetConfig) public assetConfigs; address public immutable aaveOracleAddress; // Price cache with TTL struct CachedPrice { uint256 price; uint256 timestamp; OracleSource source; } mapping(address => CachedPrice) private priceCache; event AssetConfigUpdated(address indexed asset, address chainlinkFeed, address uniswapPool); event PriceCacheUpdated(address indexed asset, uint256 price, OracleSource source); constructor(address _aaveOracleAddress, address initialOwner) Ownable(initialOwner) { require(_aaveOracleAddress != address(0), "Invalid Aave Oracle"); aaveOracleAddress = _aaveOracleAddress; } /** * @notice Configure an asset's oracle sources */ function configureAsset( address asset, address chainlinkFeed, address uniswapPool, uint256 chainlinkWeight, uint256 aaveWeight, uint256 uniswapWeight ) external onlyOwner { require(asset != address(0), "Invalid asset"); require(chainlinkWeight + aaveWeight + uniswapWeight == CONFIDENCE_SCALE, "Weights must sum to 1e18"); assetConfigs[asset] = AssetConfig({ chainlinkFeed: chainlinkFeed, aaveOracle: aaveOracleAddress, uniswapPool: uniswapPool, enabled: true, chainlinkWeight: chainlinkWeight, aaveWeight: aaveWeight, uniswapWeight: uniswapWeight }); emit AssetConfigUpdated(asset, chainlinkFeed, uniswapPool); } /** * @notice Enable or disable an asset */ function setAssetEnabled(address asset, bool enabled) external onlyOwner { require(assetConfigs[asset].chainlinkFeed != address(0) || assetConfigs[asset].aaveOracle != address(0), "Asset not configured"); assetConfigs[asset].enabled = enabled; } /** * @notice Get latest price for an asset */ function getPrice(address asset) external view override returns (PriceData memory) { return getPriceWithMaxAge(asset, MAX_PRICE_AGE); } /** * @notice Get latest price with max age requirement */ function getPriceWithMaxAge( address asset, uint256 maxAge ) public view override returns (PriceData memory) { AssetConfig memory config = assetConfigs[asset]; require(config.enabled, "Asset not enabled"); // Try cached price first if fresh CachedPrice memory cached = priceCache[asset]; if (cached.timestamp > 0 && block.timestamp - cached.timestamp <= maxAge) { return PriceData({ price: cached.price, source: cached.source, timestamp: cached.timestamp, confidence: CONFIDENCE_SCALE / 2 // Moderate confidence for cache }); } // Get prices from available sources uint256 chainlinkPrice = 0; uint256 aavePrice = 0; uint256 uniswapPrice = 0; uint256 chainlinkTime = 0; uint256 aaveTime = block.timestamp; uint256 uniswapTime = 0; // Chainlink if (config.chainlinkFeed != address(0)) { try AggregatorV3Interface(config.chainlinkFeed).latestRoundData() returns ( uint80, int256 answer, uint256, uint256 updatedAt, uint80 ) { if (answer > 0 && block.timestamp - updatedAt <= maxAge) { uint8 decimals = AggregatorV3Interface(config.chainlinkFeed).decimals(); chainlinkPrice = uint256(answer); chainlinkTime = updatedAt; // Normalize to 8 decimals if (decimals > 8) { chainlinkPrice = chainlinkPrice / (10 ** (decimals - 8)); } else if (decimals < 8) { chainlinkPrice = chainlinkPrice * (10 ** (8 - decimals)); } } } catch {} } // Aave Oracle if (config.aaveOracle != address(0)) { try IAaveOracle(config.aaveOracle).getAssetPrice(asset) returns (uint256 price) { if (price > 0) { aavePrice = price; } } catch {} } // Uniswap TWAP (simplified - would need actual TWAP implementation) // For now, return fallback if (config.uniswapPool != address(0)) { // Placeholder - would integrate with Uniswap V3 TWAP oracle uniswapTime = 0; } // Aggregate prices using weights uint256 totalWeight = 0; uint256 weightedPrice = 0; OracleSource source = OracleSource.FALLBACK; uint256 latestTimestamp = 0; if (chainlinkPrice > 0 && config.chainlinkWeight > 0) { weightedPrice += chainlinkPrice * config.chainlinkWeight; totalWeight += config.chainlinkWeight; if (chainlinkTime > latestTimestamp) { latestTimestamp = chainlinkTime; source = OracleSource.CHAINLINK; } } if (aavePrice > 0 && config.aaveWeight > 0) { weightedPrice += aavePrice * config.aaveWeight; totalWeight += config.aaveWeight; if (aaveTime > latestTimestamp) { latestTimestamp = aaveTime; source = OracleSource.AAVE; } } if (uniswapPrice > 0 && config.uniswapWeight > 0) { weightedPrice += uniswapPrice * config.uniswapWeight; totalWeight += config.uniswapWeight; if (uniswapTime > latestTimestamp) { latestTimestamp = uniswapTime; source = OracleSource.UNISWAP_TWAP; } } require(totalWeight > 0, "No valid price source"); uint256 aggregatedPrice = weightedPrice / totalWeight; uint256 confidence = (totalWeight * CONFIDENCE_SCALE) / (config.chainlinkWeight + config.aaveWeight + config.uniswapWeight); return PriceData({ price: aggregatedPrice, source: source, timestamp: latestTimestamp > 0 ? latestTimestamp : block.timestamp, confidence: confidence }); } /** * @notice Get aggregated price from multiple sources */ function getAggregatedPrice(address asset) external view override returns (uint256 price, uint256 confidence) { PriceData memory priceData = getPrice(asset); return (priceData.price, priceData.confidence); } /** * @notice Convert amount from one asset to another */ function convertAmount( address fromAsset, uint256 fromAmount, address toAsset ) external view override returns (uint256) { PriceData memory fromPrice = getPrice(fromAsset); PriceData memory toPrice = getPrice(toAsset); require(fromPrice.price > 0 && toPrice.price > 0, "Invalid prices"); // Both prices are in USD with 8 decimals // Convert: fromAmount * fromPrice / toPrice return (fromAmount * fromPrice.price) / toPrice.price; } /** * @notice Update price cache (called by keeper or internal) */ function updatePriceCache(address asset) external { PriceData memory priceData = getPriceWithMaxAge(asset, MAX_PRICE_AGE); priceCache[asset] = CachedPrice({ price: priceData.price, timestamp: block.timestamp, source: priceData.source }); emit PriceCacheUpdated(asset, priceData.price, priceData.source); } }