feat: finish up challenges
Some checks failed
Lint / ci (lts/*, ubuntu-latest) (push) Has been cancelled

This commit is contained in:
han
2026-01-26 18:22:19 +07:00
parent b330aba2b4
commit c18c66cca1
22 changed files with 8397 additions and 58 deletions

View File

@@ -0,0 +1,42 @@
{
"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
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long