Files
no_five/contracts/oracle/DBISOracleAdapter.sol
2025-11-20 15:35:25 -08:00

267 lines
9.0 KiB
Solidity

// 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);
}
}