Initial commit
This commit is contained in:
266
contracts/oracle/DBISOracleAdapter.sol
Normal file
266
contracts/oracle/DBISOracleAdapter.sol
Normal file
@@ -0,0 +1,266 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user