145 lines
4.7 KiB
Solidity
145 lines
4.7 KiB
Solidity
//SPDX-License-Identifier: MIT
|
|
pragma solidity >=0.8.0 <0.9.0;
|
|
|
|
import "./SimpleOracle.sol";
|
|
import { StatisticsUtils } from "../utils/StatisticsUtils.sol";
|
|
|
|
contract WhitelistOracle {
|
|
using StatisticsUtils for uint256[];
|
|
|
|
/////////////////
|
|
/// Errors //////
|
|
/////////////////
|
|
|
|
error OnlyOwner();
|
|
error IndexOutOfBounds();
|
|
error NoOraclesAvailable();
|
|
|
|
//////////////////////
|
|
/// State Variables //
|
|
//////////////////////
|
|
|
|
address public owner;
|
|
SimpleOracle[] public oracles;
|
|
uint256 public constant STALE_DATA_WINDOW = 24 seconds;
|
|
|
|
////////////////
|
|
/// Events /////
|
|
////////////////
|
|
|
|
event OracleAdded(address oracleAddress, address oracleOwner);
|
|
event OracleRemoved(address oracleAddress);
|
|
|
|
///////////////////
|
|
/// Modifiers /////
|
|
///////////////////
|
|
|
|
/**
|
|
* @notice Modifier to restrict function access to the contract owner
|
|
* @dev Currently disabled to make it easy for you to impersonate the owner
|
|
*/
|
|
modifier onlyOwner() {
|
|
// if (msg.sender != owner) revert OnlyOwner();
|
|
_;
|
|
}
|
|
|
|
///////////////////
|
|
/// Constructor ///
|
|
///////////////////
|
|
|
|
constructor() {
|
|
owner = msg.sender;
|
|
}
|
|
|
|
///////////////////
|
|
/// Functions /////
|
|
///////////////////
|
|
|
|
/**
|
|
* @notice Adds a new oracle to the whitelist by deploying a SimpleOracle contract (only contract owner)
|
|
* @dev Creates a new SimpleOracle instance and adds it to the oracles array.
|
|
* @param _owner The address that will own the newly created oracle and can update its price
|
|
*/
|
|
function addOracle(address _owner) public onlyOwner {
|
|
SimpleOracle newOracle = new SimpleOracle(_owner);
|
|
oracles.push(newOracle);
|
|
emit OracleAdded(address(newOracle), _owner);
|
|
}
|
|
|
|
/**
|
|
* @notice Removes an oracle from the whitelist by its array index (only contract owner)
|
|
* @dev Uses swap-and-pop pattern for gas-efficient removal. Order is not preserved.
|
|
* Reverts with IndexOutOfBounds, if the provided index is >= oracles.length.
|
|
* @param index The index of the oracle to remove from the oracles array
|
|
*/
|
|
function removeOracle(uint256 index) public onlyOwner {
|
|
uint256 oraclesSize = oracles.length;
|
|
if (index >= oraclesSize) revert IndexOutOfBounds();
|
|
|
|
address deletedOracle = address(oracles[index]);
|
|
if (index != oraclesSize - 1) {
|
|
oracles[index] = oracles[oraclesSize - 1];
|
|
}
|
|
|
|
oracles.pop();
|
|
emit OracleRemoved(deletedOracle);
|
|
}
|
|
|
|
/**
|
|
* @notice Returns the aggregated price from all active oracles using median calculation
|
|
* @dev Filters oracles with timestamps older than STALE_DATA_WINDOW, then calculates median
|
|
* of remaining valid prices. Uses StatisticsUtils for sorting and median calculation.
|
|
* @return The median price from all active oracles
|
|
*/
|
|
function getPrice() public view returns (uint256) {
|
|
if (oracles.length == 0) revert NoOraclesAvailable();
|
|
uint256[] memory prices = new uint256[](oracles.length);
|
|
uint256 priceIndex = 0;
|
|
uint256 currentTime = block.timestamp;
|
|
|
|
for (uint256 i = 0; i < oracles.length; i++) {
|
|
(uint256 price, uint256 timestamp) = oracles[i].getPrice();
|
|
if (currentTime - timestamp < STALE_DATA_WINDOW) {
|
|
prices[priceIndex] = price;
|
|
priceIndex++;
|
|
}
|
|
}
|
|
|
|
uint256[] memory fixedPrices = new uint256[](priceIndex);
|
|
for (uint256 i = 0; i < priceIndex; i++) {
|
|
fixedPrices[i] = prices[i];
|
|
}
|
|
|
|
fixedPrices.sort();
|
|
return fixedPrices.getMedian();
|
|
}
|
|
|
|
/**
|
|
* @notice Returns the addresses of all oracles that have updated their price within the last STALE_DATA_WINDOW
|
|
* @dev Iterates through all oracles and filters those with recent timestamps (within STALE_DATA_WINDOW).
|
|
* Uses a temporary array to collect active nodes, then creates a right-sized return array
|
|
* for gas optimization.
|
|
* @return An array of addresses representing the currently active oracle contracts
|
|
*/
|
|
function getActiveOracleNodes() public view returns (address[] memory) {
|
|
address[] memory activeOracles = new address[](oracles.length);
|
|
uint256 oracleIndex = 0;
|
|
uint256 currentTime = block.timestamp;
|
|
|
|
for (uint256 i = 0; i < oracles.length; i++) {
|
|
(, uint256 timestamp) = oracles[i].getPrice();
|
|
if (currentTime - timestamp < STALE_DATA_WINDOW) {
|
|
activeOracles[oracleIndex] = address(oracles[i]);
|
|
oracleIndex++;
|
|
}
|
|
}
|
|
|
|
address[] memory fixedActiveOracles = new address[](oracleIndex);
|
|
for (uint256 i = 0; i < oracleIndex; i++) {
|
|
fixedActiveOracles[i] = activeOracles[i];
|
|
}
|
|
|
|
return fixedActiveOracles;
|
|
}
|
|
}
|