{
  "language": "Solidity",
  "sources": {
    "contracts/00_Whitelist/SimpleOracle.sol": {
      "content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\ncontract SimpleOracle {\n    /////////////////\n    /// Errors //////\n    /////////////////\n\n    error OnlyOwner();\n\n    //////////////////////\n    /// State Variables //\n    //////////////////////\n\n    uint256 public price;\n    uint256 public timestamp;\n    address public owner;\n\n    ////////////////\n    /// Events /////\n    ////////////////\n\n    event PriceUpdated(uint256 newPrice);\n\n    ///////////////////\n    /// Constructor ///\n    ///////////////////\n\n    constructor(address _owner) {\n        owner = _owner;\n    }\n\n    ///////////////////\n    /// Modifiers /////\n    ///////////////////\n\n    /**\n     * @notice Modifier to restrict function access to the contract owner\n     * @dev Currently disabled to make it easy for you to impersonate the owner\n     */\n    modifier onlyOwner() {\n        // Intentionally removing the owner requirement to make it easy for you to impersonate the owner\n        // if (msg.sender != owner) revert OnlyOwner();\n        _;\n    }\n\n    ///////////////////\n    /// Functions /////\n    ///////////////////\n\n    /**\n     * @notice Updates the oracle price with a new value (only contract owner)\n     * @dev Sets the price and records the current block timestamp for freshness tracking.\n     *      Emits PriceUpdated event upon successful update.\n     * @param _newPrice The new price value to set for this oracle\n     */\n    function setPrice(uint256 _newPrice) public onlyOwner {\n        price = _newPrice;\n        timestamp = block.timestamp;\n        emit PriceUpdated(_newPrice);\n    }\n\n    /**\n     * @notice Returns the current price and its timestamp\n     * @dev Provides both the stored price value and when it was last updated.\n     *      Used by aggregators to determine price freshness.\n     * @return price The current price stored in this oracle\n     * @return timestamp The block timestamp when the price was last updated\n     */\n    function getPrice() public view returns (uint256, uint256) {\n        return (price, timestamp);\n    }\n}\n"
    },
    "contracts/00_Whitelist/WhitelistOracle.sol": {
      "content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nimport \"./SimpleOracle.sol\";\nimport { StatisticsUtils } from \"../utils/StatisticsUtils.sol\";\n\ncontract WhitelistOracle {\n    using StatisticsUtils for uint256[];\n\n    /////////////////\n    /// Errors //////\n    /////////////////\n\n    error OnlyOwner();\n    error IndexOutOfBounds();\n    error NoOraclesAvailable();\n\n    //////////////////////\n    /// State Variables //\n    //////////////////////\n\n    address public owner;\n    SimpleOracle[] public oracles;\n    uint256 public constant STALE_DATA_WINDOW = 24 seconds;\n\n    ////////////////\n    /// Events /////\n    ////////////////\n\n    event OracleAdded(address oracleAddress, address oracleOwner);\n    event OracleRemoved(address oracleAddress);\n\n    ///////////////////\n    /// Modifiers /////\n    ///////////////////\n\n    /**\n     * @notice Modifier to restrict function access to the contract owner\n     * @dev Currently disabled to make it easy for you to impersonate the owner\n     */\n    modifier onlyOwner() {\n        // if (msg.sender != owner) revert OnlyOwner();\n        _;\n    }\n\n    ///////////////////\n    /// Constructor ///\n    ///////////////////\n\n    constructor() {\n        owner = msg.sender;\n    }\n\n    ///////////////////\n    /// Functions /////\n    ///////////////////\n\n    /**\n     * @notice Adds a new oracle to the whitelist by deploying a SimpleOracle contract (only contract owner)\n     * @dev Creates a new SimpleOracle instance and adds it to the oracles array.\n     * @param _owner The address that will own the newly created oracle and can update its price\n     */\n    function addOracle(address _owner) public onlyOwner {\n      SimpleOracle newOracle = new SimpleOracle(_owner);\n      oracles.push(newOracle);\n      emit OracleAdded(address(newOracle), _owner);\n    }\n\n    /**\n     * @notice Removes an oracle from the whitelist by its array index (only contract owner)\n     * @dev Uses swap-and-pop pattern for gas-efficient removal. Order is not preserved.\n     *      Reverts with IndexOutOfBounds, if the provided index is >= oracles.length.\n     * @param index The index of the oracle to remove from the oracles array\n     */\n    function removeOracle(uint256 index) public onlyOwner {\n      uint256 oraclesSize = oracles.length;\n      if (index >= oraclesSize) revert IndexOutOfBounds();\n\n      address deletedOracle = address(oracles[index]);\n      if (index != oraclesSize - 1) {\n        oracles[index] = oracles[oraclesSize - 1];\n      }\n      \n      oracles.pop();\n      emit OracleRemoved(deletedOracle);\n    }\n\n    /**\n     * @notice Returns the aggregated price from all active oracles using median calculation\n     * @dev Filters oracles with timestamps older than STALE_DATA_WINDOW, then calculates median\n     *      of remaining valid prices. Uses StatisticsUtils for sorting and median calculation.\n     * @return The median price from all active oracles\n     */\n    function getPrice() public view returns (uint256) {\n      if (oracles.length == 0) revert NoOraclesAvailable();\n      uint256[] memory prices = new uint256[](oracles.length);\n      uint256 priceIndex = 0;\n      uint256 currentTime = block.timestamp;\n\n      for (uint256 i = 0; i < oracles.length; i++) {\n        (uint256 price, uint256 timestamp) = oracles[i].getPrice();\n\tif (currentTime - timestamp < STALE_DATA_WINDOW) {\n\t  prices[priceIndex] = price;\n\t  priceIndex++;\n\t}\n      }\n\n      uint256[] memory fixedPrices = new uint256[](priceIndex);\n      for (uint256 i = 0; i < priceIndex; i++) {\n        fixedPrices[i] = prices[i];\n      }\n\n      fixedPrices.sort();\n      return fixedPrices.getMedian();\n    }\n\n    /**\n     * @notice Returns the addresses of all oracles that have updated their price within the last STALE_DATA_WINDOW\n     * @dev Iterates through all oracles and filters those with recent timestamps (within STALE_DATA_WINDOW).\n     *      Uses a temporary array to collect active nodes, then creates a right-sized return array\n     *      for gas optimization.\n     * @return An array of addresses representing the currently active oracle contracts\n     */\n    function getActiveOracleNodes() public view returns (address[] memory) {\n      address[] memory activeOracles = new address[](oracles.length);\n      uint256 oracleIndex = 0;\n      uint256 currentTime = block.timestamp;\n\n      for (uint256 i = 0; i < oracles.length; i++) {\n        (, uint256 timestamp) = oracles[i].getPrice();\n\tif (currentTime - timestamp < STALE_DATA_WINDOW) {\n\t  activeOracles[oracleIndex] = address(oracles[i]);\n\t  oracleIndex++;\n\t}\n      }\n\n      address[] memory fixedActiveOracles = new address[](oracleIndex);\n      for (uint256 i = 0; i < oracleIndex; i++) {\n        fixedActiveOracles[i] = activeOracles[i];\n      }\n\n      return fixedActiveOracles;\n    }\n}\n"
    },
    "contracts/utils/StatisticsUtils.sol": {
      "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0 <0.9.0;\n\nlibrary StatisticsUtils {\n    /////////////////\n    /// Errors //////\n    /////////////////\n\n    error EmptyArray();\n\n    ///////////////////\n    /// Functions /////\n    ///////////////////\n\n    /**\n     * @notice Sorts an array of uint256 values in ascending order using selection sort\n     * @dev Uses selection sort algorithm which is not gas-efficient but acceptable for small arrays.\n     *      This implementation mimics the early MakerDAO Medianizer exactly.\n     *      Modifies the input array in-place.\n     * @param arr The array of uint256 values to sort in ascending order\n     */\n    function sort(uint256[] memory arr) internal pure {\n        uint256 n = arr.length;\n        for (uint256 i = 0; i < n; i++) {\n            uint256 minIndex = i;\n            for (uint256 j = i + 1; j < n; j++) {\n                if (arr[j] < arr[minIndex]) {\n                    minIndex = j;\n                }\n            }\n            if (minIndex != i) {\n                (arr[i], arr[minIndex]) = (arr[minIndex], arr[i]);\n            }\n        }\n    }\n\n    /**\n     * @notice Calculates the median value from a sorted array of uint256 values\n     * @dev For arrays with even length, returns the average of the two middle elements.\n     *      For arrays with odd length, returns the middle element.\n     *      Assumes the input array is already sorted in ascending order.\n     * @param arr The sorted array of uint256 values to calculate median from\n     * @return The median value as a uint256\n     */\n    function getMedian(uint256[] memory arr) internal pure returns (uint256) {\n        uint256 length = arr.length;\n        if (length == 0) revert EmptyArray();\n        if (length % 2 == 0) {\n            return (arr[length / 2 - 1] + arr[length / 2]) / 2;\n        } else {\n            return arr[length / 2];\n        }\n    }\n}\n"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "abi",
          "evm.bytecode",
          "evm.deployedBytecode",
          "evm.methodIdentifiers",
          "metadata",
          "devdoc",
          "userdoc",
          "storageLayout",
          "evm.gasEstimates"
        ],
        "": [
          "ast"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}