// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /** * @title OracleConsumer * @notice Example smart contracts showing how to consume data from the BFT-CRDT oracle network * @dev This demonstrates various aggregation strategies and use cases */ interface IOracleNetwork { struct PriceData { uint128 price; uint8 confidence; address oracle; uint256 timestamp; bytes32 sourceHash; } function getPrices( string calldata assetPair, uint256 startTime, uint256 endTime ) external view returns (PriceData[] memory); function getOracleReputation(address oracle) external view returns ( uint256 qualityScore, uint256 totalAttestations, uint256 anomalyReports ); } /** * @title PriceAggregator * @notice Advanced price aggregation with multiple strategies */ contract PriceAggregator { IOracleNetwork public immutable oracleNetwork; uint256 public constant MIN_SOURCES = 3; uint256 public constant OUTLIER_THRESHOLD = 500; // 5% uint256 public constant CONFIDENCE_THRESHOLD = 80; struct AggregatedPrice { uint128 price; uint8 confidence; uint256 numSources; uint256 timestamp; } mapping(string => AggregatedPrice) public latestPrices; event PriceUpdated( string indexed assetPair, uint128 price, uint8 confidence, uint256 sources ); constructor(address _oracleNetwork) { oracleNetwork = IOracleNetwork(_oracleNetwork); } /** * @notice Get aggregated price using weighted median * @param assetPair The asset pair (e.g., "ETH/USD") * @param maxAge Maximum age of price data in seconds */ function getPrice( string calldata assetPair, uint256 maxAge ) external view returns (uint128 price, uint8 confidence) { require(maxAge > 0, "Invalid max age"); // Get all prices from oracle network IOracleNetwork.PriceData[] memory prices = oracleNetwork.getPrices( assetPair, block.timestamp - maxAge, block.timestamp ); require(prices.length >= MIN_SOURCES, "Insufficient price sources"); // Calculate weighted median AggregatedPrice memory aggregated = _calculateWeightedMedian(prices); return (aggregated.price, aggregated.confidence); } /** * @notice Calculate Time-Weighted Average Price (TWAP) * @param assetPair The asset pair * @param duration Time window in seconds */ function getTWAP( string calldata assetPair, uint256 duration ) external view returns (uint128) { IOracleNetwork.PriceData[] memory prices = oracleNetwork.getPrices( assetPair, block.timestamp - duration, block.timestamp ); require(prices.length > 0, "No price data available"); uint256 weightedSum = 0; uint256 totalWeight = 0; for (uint i = 0; i < prices.length; i++) { // Weight by time and confidence uint256 timeWeight = duration - (block.timestamp - prices[i].timestamp); uint256 confWeight = prices[i].confidence; uint256 weight = timeWeight * confWeight / 100; weightedSum += prices[i].price * weight; totalWeight += weight; } return uint128(weightedSum / totalWeight); } /** * @notice Get volatility-adjusted price * @dev Uses standard deviation to adjust confidence */ function getVolatilityAdjustedPrice( string calldata assetPair, uint256 maxAge ) external view returns ( uint128 price, uint8 confidence, uint128 standardDeviation ) { IOracleNetwork.PriceData[] memory prices = oracleNetwork.getPrices( assetPair, block.timestamp - maxAge, block.timestamp ); require(prices.length >= MIN_SOURCES, "Insufficient sources"); // Remove outliers first uint128[] memory filteredPrices = _removeOutliers(prices); // Calculate mean uint256 sum = 0; for (uint i = 0; i < filteredPrices.length; i++) { sum += filteredPrices[i]; } uint128 mean = uint128(sum / filteredPrices.length); // Calculate standard deviation uint256 variance = 0; for (uint i = 0; i < filteredPrices.length; i++) { int256 diff = int256(uint256(filteredPrices[i])) - int256(uint256(mean)); variance += uint256(diff * diff); } variance = variance / filteredPrices.length; standardDeviation = uint128(_sqrt(variance)); // Adjust confidence based on volatility uint256 volatilityRatio = (standardDeviation * 10000) / mean; if (volatilityRatio < 100) { // < 1% confidence = 99; } else if (volatilityRatio < 500) { // < 5% confidence = 90; } else if (volatilityRatio < 1000) { // < 10% confidence = 70; } else { confidence = 50; } return (mean, confidence, standardDeviation); } /** * @notice Update stored price if newer data is available */ function updatePrice(string calldata assetPair) external { AggregatedPrice memory current = latestPrices[assetPair]; IOracleNetwork.PriceData[] memory prices = oracleNetwork.getPrices( assetPair, current.timestamp, block.timestamp ); if (prices.length >= MIN_SOURCES) { AggregatedPrice memory newPrice = _calculateWeightedMedian(prices); latestPrices[assetPair] = newPrice; emit PriceUpdated( assetPair, newPrice.price, newPrice.confidence, newPrice.numSources ); } } function _calculateWeightedMedian( IOracleNetwork.PriceData[] memory prices ) private view returns (AggregatedPrice memory) { // Sort prices and calculate weights uint256 length = prices.length; uint128[] memory sortedPrices = new uint128[](length); uint256[] memory weights = new uint256[](length); for (uint i = 0; i < length; i++) { sortedPrices[i] = prices[i].price; // Calculate weight based on oracle reputation and confidence (uint256 qualityScore,,) = oracleNetwork.getOracleReputation(prices[i].oracle); weights[i] = prices[i].confidence * qualityScore / 100; } // Bubble sort (gas inefficient but simple for example) for (uint i = 0; i < length - 1; i++) { for (uint j = 0; j < length - i - 1; j++) { if (sortedPrices[j] > sortedPrices[j + 1]) { // Swap prices uint128 tempPrice = sortedPrices[j]; sortedPrices[j] = sortedPrices[j + 1]; sortedPrices[j + 1] = tempPrice; // Swap weights uint256 tempWeight = weights[j]; weights[j] = weights[j + 1]; weights[j + 1] = tempWeight; } } } // Find weighted median uint256 totalWeight = 0; for (uint i = 0; i < length; i++) { totalWeight += weights[i]; } uint256 targetWeight = totalWeight / 2; uint256 cumulativeWeight = 0; uint128 medianPrice = sortedPrices[length / 2]; // fallback for (uint i = 0; i < length; i++) { cumulativeWeight += weights[i]; if (cumulativeWeight >= targetWeight) { medianPrice = sortedPrices[i]; break; } } // Calculate confidence uint8 avgConfidence = 0; for (uint i = 0; i < length; i++) { avgConfidence += prices[i].confidence; } avgConfidence = avgConfidence / uint8(length); return AggregatedPrice({ price: medianPrice, confidence: avgConfidence, numSources: length, timestamp: block.timestamp }); } function _removeOutliers( IOracleNetwork.PriceData[] memory prices ) private pure returns (uint128[] memory) { if (prices.length < 4) { // Not enough data for outlier detection uint128[] memory result = new uint128[](prices.length); for (uint i = 0; i < prices.length; i++) { result[i] = prices[i].price; } return result; } // Calculate mean uint256 sum = 0; for (uint i = 0; i < prices.length; i++) { sum += prices[i].price; } uint256 mean = sum / prices.length; // Count non-outliers uint256 validCount = 0; for (uint i = 0; i < prices.length; i++) { uint256 deviation = prices[i].price > mean ? prices[i].price - mean : mean - prices[i].price; uint256 percentDeviation = (deviation * 10000) / mean; if (percentDeviation <= OUTLIER_THRESHOLD) { validCount++; } } // Create filtered array uint128[] memory filtered = new uint128[](validCount); uint256 index = 0; for (uint i = 0; i < prices.length; i++) { uint256 deviation = prices[i].price > mean ? prices[i].price - mean : mean - prices[i].price; uint256 percentDeviation = (deviation * 10000) / mean; if (percentDeviation <= OUTLIER_THRESHOLD) { filtered[index++] = prices[i].price; } } return filtered; } function _sqrt(uint256 x) private pure returns (uint256) { if (x == 0) return 0; uint256 z = (x + 1) / 2; uint256 y = x; while (z < y) { y = z; z = (x / z + z) / 2; } return y; } } /** * @title DeFiLendingProtocol * @notice Example lending protocol using BFT-CRDT oracle */ contract DeFiLendingProtocol { PriceAggregator public immutable priceAggregator; uint256 public constant COLLATERAL_FACTOR = 8000; // 80% uint256 public constant LIQUIDATION_THRESHOLD = 8500; // 85% uint256 public constant PRICE_STALENESS = 300; // 5 minutes mapping(address => mapping(string => uint256)) public deposits; mapping(address => mapping(string => uint256)) public borrows; constructor(address _priceAggregator) { priceAggregator = PriceAggregator(_priceAggregator); } /** * @notice Calculate USD value using oracle prices */ function getCollateralValueUSD( address user, string calldata asset ) public view returns (uint256) { uint256 amount = deposits[user][asset]; if (amount == 0) return 0; (uint128 price, uint8 confidence) = priceAggregator.getPrice( string(abi.encodePacked(asset, "/USD")), PRICE_STALENESS ); require(confidence >= 80, "Price confidence too low"); // Apply conservative estimate for collateral return (amount * price * COLLATERAL_FACTOR) / 10000 / 1e6; } /** * @notice Check if a position is healthy */ function isPositionHealthy(address user) public view returns (bool) { uint256 totalCollateralUSD = 0; uint256 totalBorrowUSD = 0; // In real implementation, would iterate through all assets totalCollateralUSD += getCollateralValueUSD(user, "ETH"); totalCollateralUSD += getCollateralValueUSD(user, "BTC"); // Calculate total borrows in USD uint256 usdcBorrow = borrows[user]["USDC"]; totalBorrowUSD += usdcBorrow; // USDC is 1:1 with USD if (totalBorrowUSD == 0) return true; uint256 healthFactor = (totalCollateralUSD * 10000) / totalBorrowUSD; return healthFactor >= LIQUIDATION_THRESHOLD; } } /** * @title OptionsProtocol * @notice Example options protocol using oracle for settlement */ contract OptionsProtocol { PriceAggregator public immutable priceAggregator; struct Option { string assetPair; uint128 strikePrice; uint256 expiry; bool isCall; bool isSettled; uint128 settlementPrice; } mapping(uint256 => Option) public options; uint256 public nextOptionId; constructor(address _priceAggregator) { priceAggregator = PriceAggregator(_priceAggregator); } /** * @notice Settle an option at expiry using TWAP */ function settleOption(uint256 optionId) external { Option storage option = options[optionId]; require(!option.isSettled, "Already settled"); require(block.timestamp >= option.expiry, "Not expired"); // Use 1-hour TWAP around expiry for fair settlement uint128 settlementPrice = priceAggregator.getTWAP( option.assetPair, 3600 // 1 hour ); option.settlementPrice = settlementPrice; option.isSettled = true; // Calculate payoff uint256 payoff = 0; if (option.isCall && settlementPrice > option.strikePrice) { payoff = settlementPrice - option.strikePrice; } else if (!option.isCall && settlementPrice < option.strikePrice) { payoff = option.strikePrice - settlementPrice; } // Process payoff... } }