[Bug-Candidate]: Slither crashes when using the echidna printer in some bytes32 constants
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)
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)
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 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);
}
}
If the same constant is repeated twice is not a big deal 😄
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.
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 != "":