slither icon indicating copy to clipboard operation
slither copied to clipboard

[Bug-Candidate]: Slither crashes when using the echidna printer in some bytes32 constants

Open gustavo-grieco opened this issue 7 months ago • 6 comments

Describe the issue:

Slither crashes when trying to print some constant using the echidna printer --print echidna

Code example to reproduce the issue:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

interface IUmbrellaFeeds {
    struct PriceData {
        uint8 data;
        uint24 heartbeat;
        uint32 timestamp;
        uint128 price;
    }

    function getPriceData(bytes32 _key) external view returns (PriceData memory data);
}


interface IUmbrellaRegistry {
    function getAddress(bytes32 _bytes) external view returns (address);
}

/// @title UmbrellaAggregator
/// @notice Wraps umbrella price feed in an aggregator interface
contract UmbrellaAggregator {
    bytes32 public constant FEEDS_KEY_NAME = bytes32("UmbrellaFeeds");

    IUmbrellaRegistry public immutable registry;
    bytes32 public immutable key;

    constructor(bytes32 _key, IUmbrellaRegistry _registry) {
        key = _key;
        registry = _registry;
    }

    function latestRoundData() public view returns (uint80, int256 answer, uint256, uint256 updatedAt, uint80) {
        _getFeeds().getPriceData(key);
    }

    function _getFeeds() private view returns (IUmbrellaFeeds) {
        return IUmbrellaFeeds(registry.getAddress(FEEDS_KEY_NAME));
    }
}

Version:

0.11.3

Relevant log output:

Traceback (most recent call last):
  File "/opt/homebrew/bin/slither", line 8, in <module>
    sys.exit(main())
             ~~~~^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/__main__.py", line 776, in main
    main_impl(all_detector_classes=detectors, all_printer_classes=printers)
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/__main__.py", line 882, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
        ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/__main__.py", line 107, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
        ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/__main__.py", line 87, in process_single
    return _process(slither, detector_classes, printer_classes)
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/__main__.py", line 143, in _process
    printer_results = slither.run_printers()
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/slither.py", line 302, in run_printers
    return [p.output(self._crytic_compile.target).data for p in self._printers]
            ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/printers/guidance/echidna.py", line 454, in output
    (cst_used, cst_used_in_binary) = _extract_constants(contracts)
                                     ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/printers/guidance/echidna.py", line 285, in _extract_constants
    _extract_constants_from_irs(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        function.all_slithir_operations(),
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
        context_explored,
        ^^^^^^^^^^^^^^^^^
    )
    ^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/printers/guidance/echidna.py", line 266, in _extract_constants_from_irs
    _extract_constant_from_read(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        ir, r, all_cst_used, all_cst_used_in_binary, context_explored
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/printers/guidance/echidna.py", line 199, in _extract_constant_from_read
    value = ConstantFolding(var_read.expression, value_type).result()
            ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/visitors/expression/constants_folding.py", line 60, in __init__
    super().__init__(expression)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/visitors/expression/expression.py", line 59, in __init__
    self._visit_expression(self.expression)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/visitors/expression/expression.py", line 78, in _visit_expression
    self._post_visit(expression)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/visitors/expression/expression.py", line 291, in _post_visit
    self._post_type_conversion(expression)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/visitors/expression/constants_folding.py", line 446, in _post_type_conversion
    value = convert_string_to_fraction(expr.converted_value)
  File "/opt/homebrew/Cellar/slither-analyzer/0.11.3/libexec/lib/python3.13/site-packages/slither/utils/integer_conversion.py", line 20, in convert_string_to_fraction
    base, expo = val.split("e") if "e" in val else val.split("E")
    ^^^^^^^^^^
ValueError: too many values to unpack (expected 2)

gustavo-grieco avatar May 15 '25 15:05 gustavo-grieco

Looks like I'm experiencing the same bug:

❯ slither . --print echidna
'forge clean' running (wd: /home/rappie/Desktop/targets/pinto/protocol)
'forge config --json' running
'forge build --build-info --skip */test/** */script/** --force' running (wd: /home/rappie/Desktop/targets/pinto/protocol)
Traceback (most recent call last):
  File "/home/rappie/Desktop/repos/slither.git/env/bin/slither", line 7, in <module>
    sys.exit(main())
             ~~~~^^
  File "/home/rappie/Desktop/repos/slither.git/slither/__main__.py", line 776, in main
    main_impl(all_detector_classes=detectors, all_printer_classes=printers)
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/__main__.py", line 882, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
        ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/__main__.py", line 107, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
        ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/__main__.py", line 87, in process_single
    return _process(slither, detector_classes, printer_classes)
  File "/home/rappie/Desktop/repos/slither.git/slither/__main__.py", line 143, in _process
    printer_results = slither.run_printers()
  File "/home/rappie/Desktop/repos/slither.git/slither/slither.py", line 302, in run_printers
    return [p.output(self._crytic_compile.target).data for p in self._printers]
            ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/printers/guidance/echidna.py", line 454, in output
    (cst_used, cst_used_in_binary) = _extract_constants(contracts)
                                     ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/printers/guidance/echidna.py", line 285, in _extract_constants
    _extract_constants_from_irs(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        function.all_slithir_operations(),
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
        context_explored,
        ^^^^^^^^^^^^^^^^^
    )
    ^
  File "/home/rappie/Desktop/repos/slither.git/slither/printers/guidance/echidna.py", line 266, in _extract_constants_from_irs
    _extract_constant_from_read(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        ir, r, all_cst_used, all_cst_used_in_binary, context_explored
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/home/rappie/Desktop/repos/slither.git/slither/printers/guidance/echidna.py", line 199, in _extract_constant_from_read
    value = ConstantFolding(var_read.expression, value_type).result()
            ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/visitors/expression/constants_folding.py", line 60, in __init__
    super().__init__(expression)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/visitors/expression/expression.py", line 59, in __init__
    self._visit_expression(self.expression)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/visitors/expression/expression.py", line 76, in _visit_expression
    visitor(expression)
    ~~~~~~~^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/visitors/expression/expression.py", line 94, in _visit_call_expression
    self._visit_expression(arg)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/visitors/expression/expression.py", line 78, in _visit_expression
    self._post_visit(expression)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/visitors/expression/expression.py", line 291, in _post_visit
    self._post_type_conversion(expression)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/home/rappie/Desktop/repos/slither.git/slither/visitors/expression/constants_folding.py", line 446, in _post_type_conversion
    value = convert_string_to_fraction(expr.converted_value)
  File "/home/rappie/Desktop/repos/slither.git/slither/utils/integer_conversion.py", line 19, in convert_string_to_fraction
    base, expo = val.split("e") if "e" in val else val.split("E")
    ^^^^^^^^^^
ValueError: too many values to unpack (expected 2)

rappie avatar Aug 23 '25 13:08 rappie

I added the fix in slither for this to not make it crashes but a string is implicitly convertible to bytesN so you don't need the cast, if even without the cast it still crashes for you then it may be another issue

smonicas avatar Aug 23 '25 14:08 smonicas

@smonicas there's a deeper issue to how strings are represented; for example in this contract both of these constants look the same to Slither while they're different values:

contract C {
    bytes32 public constant C1 = bytes32("0xaaeeaa");
    bytes32 public constant C2 = bytes32(hex"aaeeaa");

    function f() public {
        assert(C1 == C2);
    }
}

elopez avatar Aug 23 '25 16:08 elopez

If the same constant is repeated twice is not a big deal 😄

gustavo-grieco avatar Aug 23 '25 18:08 gustavo-grieco

If the same constant is repeated twice is not a big deal 😄

that's the thing, it's not the same constant 😅 but slither sees it as the same.

elopez avatar Aug 23 '25 19:08 elopez

This change converts everything in strings to hex notation and therefore avoids these "confusion" sorts of issues, but it's probably not a good fix for long-term. Just sharing it here in case anyone needs a workaround:

diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py
index afa5c367b..86f6b68cf 100644
--- a/slither/solc_parsing/expressions/expression_parsing.py
+++ b/slither/solc_parsing/expressions/expression_parsing.py
@@ -438,6 +438,8 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
         if is_compact_ast:
             value = expression.get("value", None)
             if value:
+                if expression["typeDescriptions"]["typeString"].startswith("literal_string "):
+                    value = "0x" + expression["hexValue"]
                 if "subdenomination" in expression and expression["subdenomination"]:
                     subdenomination = expression["subdenomination"]
             elif not value and value != "":

elopez avatar Aug 24 '25 15:08 elopez