From f25344c6a29a5dfd501f00a474fb4b9c520fc11a Mon Sep 17 00:00:00 2001 From: Simone Date: Tue, 8 Oct 2024 18:32:11 +0200 Subject: [PATCH] Add Pyth unchecked confidence detector --- slither/detectors/all_detectors.py | 2 + .../detectors/statements/pyth_unchecked.py | 79 +++++++ .../statements/pyth_unchecked_confidence.py | 50 +++++ ..._8_20_pyth_unchecked_confidence_sol__0.txt | 3 + .../0.8.20/pyth_unchecked_confidence.sol | 193 ++++++++++++++++++ .../pyth_unchecked_confidence.sol-0.8.20.zip | Bin 0 -> 10811 bytes tests/e2e/detectors/test_detectors.py | 10 + 7 files changed, 337 insertions(+) create mode 100644 slither/detectors/statements/pyth_unchecked.py create mode 100644 slither/detectors/statements/pyth_unchecked_confidence.py create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_PythUncheckedConfidence_0_8_20_pyth_unchecked_confidence_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol create mode 100644 tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol-0.8.20.zip diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 44a168c2b..75e838e53 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -97,5 +97,7 @@ from .statements.tautological_compare import TautologicalCompare from .statements.return_bomb import ReturnBomb from .functions.out_of_order_retryable import OutOfOrderRetryable +from .statements.pyth_unchecked_confidence import PythUncheckedConfidence +from .statements.pyth_unchecked_publishtime import PythUncheckedPublishTime # from .statements.unused_import import UnusedImport diff --git a/slither/detectors/statements/pyth_unchecked.py b/slither/detectors/statements/pyth_unchecked.py new file mode 100644 index 000000000..959aee6a5 --- /dev/null +++ b/slither/detectors/statements/pyth_unchecked.py @@ -0,0 +1,79 @@ +from typing import List + +from slither.detectors.abstract_detector import ( + AbstractDetector, + DETECTOR_INFO, +) +from slither.utils.output import Output +from slither.slithir.operations import Member, Binary, Assignment + + +class PythUnchecked(AbstractDetector): + """ + Documentation: This detector finds deprecated Pyth function calls + """ + + # To be overriden in the derived class + PYTH_FUNCTIONS = [] + PYTH_FIELD = "" + + # pylint: disable=too-many-nested-blocks + def _detect(self) -> List[Output]: + results: List[Output] = [] + + for contract in self.compilation_unit.contracts_derived: + for target_contract, ir in contract.all_high_level_calls: + if target_contract.name == "IPyth" and ir.function_name in self.PYTH_FUNCTIONS: + # We know for sure the second IR in the node is an Assignment operation of the TMP variable. Example: + # Expression: price = pyth.getEmaPriceNoOlderThan(id,age) + # IRs: + # TMP_0(PythStructs.Price) = HIGH_LEVEL_CALL, dest:pyth(IPyth), function:getEmaPriceNoOlderThan, arguments:['id', 'age'] + # price(PythStructs.Price) := TMP_0(PythStructs.Price) + assert isinstance(ir.node.irs[1], Assignment) + return_variable = ir.node.irs[1].lvalue + checked = False + + possible_unchecked_variable_ir = None + nodes = ir.node.sons + visited = set() + while nodes: + if checked: + break + next_node = nodes[0] + nodes = nodes[1:] + + for node_ir in next_node.all_slithir_operations(): + # We are accessing the unchecked_var field of the returned Price struct + if ( + isinstance(node_ir, Member) + and node_ir.variable_left == return_variable + and node_ir.variable_right.name == self.PYTH_FIELD + ): + possible_unchecked_variable_ir = node_ir.lvalue + # We assume that if unchecked_var happens to be inside a binary operation is checked + if ( + isinstance(node_ir, Binary) + and possible_unchecked_variable_ir is not None + and possible_unchecked_variable_ir in node_ir.read + ): + checked = True + break + + if next_node not in visited: + visited.add(next_node) + for son in next_node.sons: + if son not in visited: + nodes.append(son) + + if not checked: + info: DETECTOR_INFO = [ + f"Pyth price {self.PYTH_FIELD} field is not checked in ", + ir.node.function, + "\n\t- ", + ir.node, + "\n", + ] + res = self.generate_result(info) + results.append(res) + + return results diff --git a/slither/detectors/statements/pyth_unchecked_confidence.py b/slither/detectors/statements/pyth_unchecked_confidence.py new file mode 100644 index 000000000..2e99851a8 --- /dev/null +++ b/slither/detectors/statements/pyth_unchecked_confidence.py @@ -0,0 +1,50 @@ +from slither.detectors.abstract_detector import DetectorClassification +from slither.detectors.statements.pyth_unchecked import PythUnchecked + + +class PythUncheckedConfidence(PythUnchecked): + """ + Documentation: This detector finds when the confidence level of a Pyth price is not checked + """ + + ARGUMENT = "pyth-unchecked-confidence" + HELP = "Detect when the confidence level of a Pyth price is not checked" + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#pyth-unchecked-confidence" + WIKI_TITLE = "Pyth unchecked confidence level" + WIKI_DESCRIPTION = "Detect when the confidence level of a Pyth price is not checked" + WIKI_RECOMMENDATION = "Check the confidence level of a Pyth price. Visit https://docs.pyth.network/price-feeds/best-practices#confidence-intervals for more information." + + WIKI_EXPLOIT_SCENARIO = """ +```solidity +import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; +import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; + +contract C { + IPyth pyth; + + constructor(IPyth _pyth) { + pyth = _pyth; + } + + function bad(bytes32 id, uint256 age) public { + PythStructs.Price memory price = pyth.getEmaPriceNoOlderThan(id, age); + // Use price + } +} +``` +The function `A` uses the price without checking its confidence level. +""" + + PYTH_FUNCTIONS = [ + "getEmaPrice", + "getEmaPriceNoOlderThan", + "getEmaPriceUnsafe", + "getPrice", + "getPriceNoOlderThan", + "getPriceUnsafe", + ] + + PYTH_FIELD = "conf" diff --git a/tests/e2e/detectors/snapshots/detectors__detector_PythUncheckedConfidence_0_8_20_pyth_unchecked_confidence_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_PythUncheckedConfidence_0_8_20_pyth_unchecked_confidence_sol__0.txt new file mode 100644 index 000000000..ae0dc2ae2 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_PythUncheckedConfidence_0_8_20_pyth_unchecked_confidence_sol__0.txt @@ -0,0 +1,3 @@ +Pyth price conf field is not checked in C.bad(bytes32,uint256) (tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol#171-175) + - price = pyth.getEmaPriceNoOlderThan(id,age) (tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol#172) + diff --git a/tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol b/tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol new file mode 100644 index 000000000..58880c382 --- /dev/null +++ b/tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol @@ -0,0 +1,193 @@ +contract PythStructs { + // A price with a degree of uncertainty, represented as a price +- a confidence interval. + // + // The confidence interval roughly corresponds to the standard error of a normal distribution. + // Both the price and confidence are stored in a fixed-point numeric representation, + // `x * (10^expo)`, where `expo` is the exponent. + // + // Please refer to the documentation at https://docs.pyth.network/consumers/best-practices for how + // to how this price safely. + struct Price { + // Price + int64 price; + // Confidence interval around the price + uint64 conf; + // Price exponent + int32 expo; + // Unix timestamp describing when the price was published + uint publishTime; + } + + // PriceFeed represents a current aggregate price from pyth publisher feeds. + struct PriceFeed { + // The price ID. + bytes32 id; + // Latest available price + Price price; + // Latest available exponentially-weighted moving average price + Price emaPrice; + } +} + +interface IPyth { + /// @notice Returns the period (in seconds) that a price feed is considered valid since its publish time + function getValidTimePeriod() external view returns (uint validTimePeriod); + + /// @notice Returns the price and confidence interval. + /// @dev Reverts if the price has not been updated within the last `getValidTimePeriod()` seconds. + /// @param id The Pyth Price Feed ID of which to fetch the price and confidence interval. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getPrice( + bytes32 id + ) external view returns (PythStructs.Price memory price); + + /// @notice Returns the exponentially-weighted moving average price and confidence interval. + /// @dev Reverts if the EMA price is not available. + /// @param id The Pyth Price Feed ID of which to fetch the EMA price and confidence interval. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getEmaPrice( + bytes32 id + ) external view returns (PythStructs.Price memory price); + + /// @notice Returns the price of a price feed without any sanity checks. + /// @dev This function returns the most recent price update in this contract without any recency checks. + /// This function is unsafe as the returned price update may be arbitrarily far in the past. + /// + /// Users of this function should check the `publishTime` in the price to ensure that the returned price is + /// sufficiently recent for their application. If you are considering using this function, it may be + /// safer / easier to use either `getPrice` or `getPriceNoOlderThan`. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getPriceUnsafe( + bytes32 id + ) external view returns (PythStructs.Price memory price); + + /// @notice Returns the price that is no older than `age` seconds of the current time. + /// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in + /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently + /// recently. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getPriceNoOlderThan( + bytes32 id, + uint age + ) external view returns (PythStructs.Price memory price); + + /// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks. + /// @dev This function returns the same price as `getEmaPrice` in the case where the price is available. + /// However, if the price is not recent this function returns the latest available price. + /// + /// The returned price can be from arbitrarily far in the past; this function makes no guarantees that + /// the returned price is recent or useful for any particular application. + /// + /// Users of this function should check the `publishTime` in the price to ensure that the returned price is + /// sufficiently recent for their application. If you are considering using this function, it may be + /// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getEmaPriceUnsafe( + bytes32 id + ) external view returns (PythStructs.Price memory price); + + /// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds + /// of the current time. + /// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in + /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently + /// recently. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getEmaPriceNoOlderThan( + bytes32 id, + uint age + ) external view returns (PythStructs.Price memory price); + + /// @notice Update price feeds with given update messages. + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// Prices will be updated if they are more recent than the current stored prices. + /// The call will succeed even if the update is not the most recent. + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid. + /// @param updateData Array of price update data. + function updatePriceFeeds(bytes[] calldata updateData) external payable; + + /// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is + /// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the + /// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`. + /// + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// + /// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime + /// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have + /// a newer or equal publish time than the given publish time, it will reject the transaction to save gas. + /// Otherwise, it calls updatePriceFeeds method to update the prices. + /// + /// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]` + function updatePriceFeedsIfNecessary( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64[] calldata publishTimes + ) external payable; + + /// @notice Returns the required fee to update an array of price updates. + /// @param updateData Array of price update data. + /// @return feeAmount The required fee in Wei. + function getUpdateFee( + bytes[] calldata updateData + ) external view returns (uint feeAmount); + + /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published + /// within `minPublishTime` and `maxPublishTime`. + /// + /// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price; + /// otherwise, please consider using `updatePriceFeeds`. This method does not store the price updates on-chain. + /// + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// + /// + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is + /// no update for any of the given `priceIds` within the given time range. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. + /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. + /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). + function parsePriceFeedUpdates( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); +} + + +contract C { + IPyth pyth; + + constructor(IPyth _pyth) { + pyth = _pyth; + } + + function bad(bytes32 id, uint256 age) public { + PythStructs.Price memory price = pyth.getEmaPriceNoOlderThan(id, age); + require(price.publishTime > block.timestamp - 120); + // Use price + } + + function good(bytes32 id, uint256 age) public { + PythStructs.Price memory price = pyth.getEmaPriceNoOlderThan(id, age); + require(price.conf < 10000); + require(price.publishTime > block.timestamp - 120); + // Use price + } + + function good2(bytes32 id, uint256 age) public { + PythStructs.Price memory price = pyth.getEmaPriceNoOlderThan(id, age); + require(price.publishTime > block.timestamp - 120); + if (price.conf >= 10000) { + revert(); + } + // Use price + } + +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol-0.8.20.zip b/tests/e2e/detectors/test_data/pyth-unchecked-confidence/0.8.20/pyth_unchecked_confidence.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..6e5fa1b9f1b470d9ad9ca7632115fd7f49e67ff9 GIT binary patch literal 10811 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 WvXD^!J%awHhX2{o{}53SkpBS-=ILPo literal 0 HcmV?d00001 diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index 2c6a5f55a..5efd26153 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1714,6 +1714,16 @@ def id_test(test_item: Test): "out_of_order_retryable.sol", "0.8.20", ), + Test( + all_detectors.PythUncheckedConfidence, + "pyth_unchecked_confidence.sol", + "0.8.20", + ), + Test( + all_detectors.PythUncheckedPublishTime, + "pyth_unchecked_publishtime.sol", + "0.8.20", + ), # Test( # all_detectors.UnusedImport, # "ConstantContractLevelUsedInContractTest.sol",