Merge branch 'dev' into dev-slither-format-tool-only-new

pull/238/head
Josselin 5 years ago
commit ab40693de7
  1. 24
      CONTRIBUTING.md
  2. 32
      README.md
  3. 26
      examples/printers/constructors.sol
  4. 2
      scripts/tests_generate_expected_json_4.sh
  5. 1
      scripts/tests_generate_expected_json_5.sh
  6. 7
      scripts/travis_install.sh
  7. 1
      scripts/travis_test_5.sh
  8. 8
      setup.py
  9. 179
      slither/__main__.py
  10. 56
      slither/core/cfg/node.py
  11. 4
      slither/core/children/child_node.py
  12. 79
      slither/core/declarations/function.py
  13. 10
      slither/core/expressions/literal.py
  14. 5
      slither/core/slither_core.py
  15. 13
      slither/core/source_mapping/source_mapping.py
  16. 1
      slither/detectors/all_detectors.py
  17. 5
      slither/detectors/naming_convention/naming_convention.py
  18. 46
      slither/detectors/operations/void_constructor.py
  19. 37
      slither/detectors/reentrancy/reentrancy.py
  20. 1
      slither/detectors/statements/too_many_digits.py
  21. 1
      slither/printers/all_printers.py
  22. 47
      slither/printers/summary/constructor_calls.py
  23. 8
      slither/printers/summary/slithir.py
  24. 19
      slither/slither.py
  25. 5
      slither/slithir/convert.py
  26. 1
      slither/slithir/operations/__init__.py
  27. 13
      slither/slithir/operations/call.py
  28. 50
      slither/slithir/operations/high_level_call.py
  29. 12
      slither/slithir/operations/library_call.py
  30. 14
      slither/slithir/operations/low_level_call.py
  31. 40
      slither/slithir/operations/new_contract.py
  32. 14
      slither/slithir/operations/nop.py
  33. 3
      slither/slithir/operations/send.py
  34. 2
      slither/slithir/operations/transfer.py
  35. 8
      slither/slithir/variables/constant.py
  36. 14
      slither/solc_parsing/declarations/contract.py
  37. 32
      slither/solc_parsing/declarations/function.py
  38. 51
      slither/solc_parsing/expressions/expression_parsing.py
  39. 15
      slither/solc_parsing/slitherSolc.py
  40. 0
      slither/tools/__init__.py
  41. 6
      slither/tools/demo/README.md
  42. 0
      slither/tools/demo/__init__.py
  43. 38
      slither/tools/demo/__main__.py
  44. 0
      slither/tools/possible_paths/__init__.py
  45. 0
      slither/tools/possible_paths/__main__.py
  46. 0
      slither/tools/possible_paths/possible_paths.py
  47. 0
      slither/tools/similarity/__init__.py
  48. 0
      slither/tools/similarity/__main__.py
  49. 0
      slither/tools/similarity/cache.py
  50. 0
      slither/tools/similarity/encode.py
  51. 0
      slither/tools/similarity/info.py
  52. 0
      slither/tools/similarity/model.py
  53. 0
      slither/tools/similarity/plot.py
  54. 0
      slither/tools/similarity/similarity.py
  55. 0
      slither/tools/similarity/test.py
  56. 0
      slither/tools/similarity/train.py
  57. 0
      slither/tools/slither_format/.gitignore
  58. 0
      slither/tools/slither_format/README.md
  59. 0
      slither/tools/slither_format/__init__.py
  60. 0
      slither/tools/slither_format/__main__.py
  61. 0
      slither/tools/slither_format/exceptions.py
  62. 0
      slither/tools/slither_format/formatters/__init__.py
  63. 0
      slither/tools/slither_format/formatters/constable_states.py
  64. 0
      slither/tools/slither_format/formatters/constant_function.py
  65. 0
      slither/tools/slither_format/formatters/external_function.py
  66. 0
      slither/tools/slither_format/formatters/naming_convention.py
  67. 0
      slither/tools/slither_format/formatters/pragma.py
  68. 0
      slither/tools/slither_format/formatters/solc_version.py
  69. 0
      slither/tools/slither_format/formatters/unused_state.py
  70. 0
      slither/tools/slither_format/slither_format.py
  71. 0
      slither/tools/slither_format/tests/.gitignore
  72. 0
      slither/tools/slither_format/tests/__init__.py
  73. 0
      slither/tools/slither_format/tests/real_world/0x0000000000b3F879cb30FE243b4Dfee438691c04_GasToken2.sol
  74. 0
      slither/tools/slither_format/tests/real_world/0x006bea43baa3f7a6f765f14f10a1a1b08334ef45_StoxSmartToken.sol
  75. 0
      slither/tools/slither_format/tests/real_world/0x0235fe624e044a05eed7a43e16e3083bc8a4287a_OriginalToken.sol
  76. 0
      slither/tools/slither_format/tests/real_world/0x05cf67329a262818e67c080e9d511a34d36152c0_MultiSigWallet.sol
  77. 0
      slither/tools/slither_format/tests/real_world/0x05f4a42e251f2d52b8ed15e9fedaacfcef1fad27_ZilliqaToken.sol
  78. 0
      slither/tools/slither_format/tests/real_world/0x06012c8cf97bead5deae237070f9587f8e7a266d_KittyCore.sol
  79. 0
      slither/tools/slither_format/tests/real_world/0x5d0d76787d9d564061dd23f8209f804a3b8ad2f2_FoMo3Dlong.sol
  80. 0
      slither/tools/slither_format/tests/real_world/0xbf45f4280cfbe7c2d2515a7d984b8c71c15e82b7_EnclavesDEXProxy.sol
  81. 0
      slither/tools/slither_format/tests/real_world/0xc6725ae749677f21e4d8f85f41cfb6de49b9db29_BancorConverter.sol
  82. 0
      slither/tools/slither_format/tests/real_world/0xf5ed2dc77f0d1ea7f106ecbd1850e406adc41b51_OceanToken.sol
  83. 0
      slither/tools/slither_format/tests/runSlitherFormat.py
  84. 0
      slither/tools/slither_format/tests/run_all_tests.py
  85. 0
      slither/tools/slither_format/tests/test_constable_states.py
  86. 0
      slither/tools/slither_format/tests/test_constant_function.py
  87. 0
      slither/tools/slither_format/tests/test_data/const_state_variables.sol
  88. 0
      slither/tools/slither_format/tests/test_data/constant-0.5.1.sol
  89. 0
      slither/tools/slither_format/tests/test_data/constant.sol
  90. 0
      slither/tools/slither_format/tests/test_data/detector_combinations.sol
  91. 0
      slither/tools/slither_format/tests/test_data/external_function.sol
  92. 0
      slither/tools/slither_format/tests/test_data/external_function_2.sol
  93. 0
      slither/tools/slither_format/tests/test_data/external_function_import.sol
  94. 0
      slither/tools/slither_format/tests/test_data/naming_convention.sol
  95. 0
      slither/tools/slither_format/tests/test_data/naming_convention_contract.sol
  96. 0
      slither/tools/slither_format/tests/test_data/naming_convention_enum.sol
  97. 0
      slither/tools/slither_format/tests/test_data/naming_convention_event.sol
  98. 0
      slither/tools/slither_format/tests/test_data/naming_convention_function.sol
  99. 0
      slither/tools/slither_format/tests/test_data/naming_convention_modifier.sol
  100. 0
      slither/tools/slither_format/tests/test_data/naming_convention_parameter.sol
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,24 @@
# Contributing to Slither
First, thanks for your interest in contributing to Slither! We welcome and appreciate all contributions, including bug reports, feature suggestions, tutorials/blog posts, and code improvements.
If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels.
# Bug reports and feature suggestions
Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email opensource@trailofbits.com instead.
# Questions
Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel).
# Code
Slither uses the pull request contribution model. Please make an account on Github, fork this repo, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/).
Some pull request guidelines:
- Work from the [`dev`](https://github.com/crytic/slither/tree/dev) branch. We performed extensive tests prior to merging anything to `master`, working from `dev` will allow us to merge your work faster.
- Minimize irrelevant changes (formatting, whitespace, etc) to code that would otherwise not be touched by this patch. Save formatting or style corrections for a separate pull request that does not make any semantic changes.
- When possible, large changes should be split up into smaller focused pull requests.
- Fill out the pull request description with a summary of what your patch does, key changes that have been made, and any further points of discussion, if applicable.
- Title your pull request with a brief description of what it's changing. "Fixes #123" is a good comment to add to the description, but makes for an unclear title on its own.
# Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).

@ -61,20 +61,22 @@ Num | Detector | What it Detects | Impact | Confidence
20 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
21 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
22 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
23 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop) | Low | Medium
24 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
25 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
26 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
27 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
28 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
29 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
30 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
31 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
32 | `solc-version` | [Incorrect Solidity version (< 0.4.24 or complex pragma)](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
33 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High
34 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
35 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
36 | `external-function` | [Public function that could be declared as external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external) | Optimization | High
23 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
24 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop) | Low | Medium
25 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
26 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
27 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
28 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
29 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
30 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
31 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
32 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
33 | `solc-version` | [Incorrect Solidity version (< 0.4.24 or complex pragma)](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
34 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High
35 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
36 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
37 | `external-function` | [Public function that could be declared as external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external) | Optimization | High
[Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors.
@ -113,7 +115,7 @@ $ pip install slither-analyzer
### Using Git
```bash
$ git clone https://github.com/trailofbits/slither.git && cd slither
$ git clone https://github.com/crytic/slither.git && cd slither
$ python setup.py install
```

@ -0,0 +1,26 @@
pragma solidity >=0.4.22 <0.6.0;
contract test{
uint a;
constructor()public{
a =5;
}
}
contract test2 is test{
constructor()public{
a=10;
}
}
contract test3 is test2{
address owner;
bytes32 name;
constructor(bytes32 _name)public{
owner = msg.sender;
name = _name;
a=20;
}
function print() public returns(uint b){
b=a;
}
}

@ -21,7 +21,7 @@ generate_expected_json(){
}
generate_expected_json tests/deprecated_calls.sol "deprecated-standards"
#generate_expected_json tests/deprecated_calls.sol "deprecated-standards"
#generate_expected_json tests/erc20_indexed.sol "erc20-indexed"
#generate_expected_json tests/incorrect_erc20_interface.sol "erc20-interface"
#generate_expected_json tests/incorrect_erc721_interface.sol "erc721-interface"

@ -20,6 +20,7 @@ generate_expected_json(){
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i
}
#generate_expected_json tests/void-cst.sol "void-cst"
#generate_expected_json tests/solc_version_incorrect_05.ast.json "solc-version"
#generate_expected_json tests/uninitialized-0.5.1.sol "uninitialized-state"
#generate_expected_json tests/backdoor.sol "backdoor"

@ -1,12 +1,5 @@
#!/usr/bin/env bash
# TODO: temporary until the next crytic-compile release
git clone https://github.com/crytic/crytic-compile
cd crytic-compile
git checkout dev
python setup.py install
cd ..
python setup.py install
# Used by travis_test.sh
pip install deepdiff

@ -69,6 +69,7 @@ test_slither(){
}
test_slither tests/void-cst.sol "void-cst"
test_slither tests/solc_version_incorrect_05.ast.json "solc-version"
test_slither tests/unchecked_lowlevel-0.5.1.sol "unchecked-lowlevel"
test_slither tests/unchecked_send-0.5.1.sol "unchecked-send"

@ -14,10 +14,10 @@ setup(
entry_points={
'console_scripts': [
'slither = slither.__main__:main',
'slither-check-upgradeability = utils.upgradeability.__main__:main',
'slither-find-paths = utils.possible_paths.__main__:main',
'slither-simil = utils.similarity.__main__:main',
'slither-format = utils.slither_format.__main__:main'
'slither-check-upgradeability = slither.tools.upgradeability.__main__:main',
'slither-find-paths = slither.tools.possible_paths.__main__:main',
'slither-simil = slither.tools.similarity.__main__:main'
'slither-format = slither.tools.slither_format.__main__:main'
]
}
)

@ -11,6 +11,7 @@ import traceback
from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser
from crytic_compile.platform.standard import generate_standard_export
from slither.detectors import all_detectors
from slither.detectors.abstract_detector import (AbstractDetector,
@ -18,12 +19,13 @@ from slither.detectors.abstract_detector import (AbstractDetector,
from slither.printers import all_printers
from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither
from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, yellow, set_colorization_enabled
from slither.utils.command_line import (output_detectors, output_results_to_markdown,
output_detectors_json, output_printers,
output_detectors_json, output_printers, output_printers_json,
output_to_markdown, output_wiki, defaults_flag_in_config,
read_config_file)
from crytic_compile import is_supported
read_config_file, JSON_OUTPUT_TYPES)
from crytic_compile import compile_all, is_supported
from slither.exceptions import SlitherException
logging.basicConfig()
@ -35,7 +37,8 @@ logger = logging.getLogger("Slither")
###################################################################################
###################################################################################
def process(filename, args, detector_classes, printer_classes):
def process_single(target, args, detector_classes, printer_classes):
"""
The core high-level code for running Slither static analysis.
@ -45,13 +48,26 @@ def process(filename, args, detector_classes, printer_classes):
ast = '--ast-compact-json'
if args.legacy_ast:
ast = '--ast-json'
args.filter_paths = parse_filter_paths(args)
slither = Slither(filename,
slither = Slither(target,
ast_format=ast,
**vars(args))
return _process(slither, detector_classes, printer_classes)
def process_all(target, args, detector_classes, printer_classes):
compilations = compile_all(target, **vars(args))
slither_instances = []
results = []
analyzed_contracts_count = 0
for compilation in compilations:
(slither, current_results, current_analyzed_count) = process_single(compilation, args, detector_classes, printer_classes)
results.extend(current_results)
slither_instances.append(slither)
analyzed_contracts_count += current_analyzed_count
return slither_instances, results, analyzed_contracts_count
def _process(slither, detector_classes, printer_classes):
for detector_cls in detector_classes:
slither.register_detector(detector_cls)
@ -72,10 +88,10 @@ def _process(slither, detector_classes, printer_classes):
slither.run_printers() # Currently printers does not return results
return results, analyzed_contracts_count
return slither, results, analyzed_contracts_count
def process_files(filenames, args, detector_classes, printer_classes):
def process_from_asts(filenames, args, detector_classes, printer_classes):
all_contracts = []
for filename in filenames:
@ -83,11 +99,9 @@ def process_files(filenames, args, detector_classes, printer_classes):
contract_loaded = json.load(f)
all_contracts.append(contract_loaded['ast'])
slither = Slither(all_contracts,
filter_paths=parse_filter_paths(args),
**vars(args))
return process_single(all_contracts, args, detector_classes, printer_classes)
return _process(slither, detector_classes, printer_classes)
# endregion
###################################################################################
@ -97,26 +111,15 @@ def process_files(filenames, args, detector_classes, printer_classes):
###################################################################################
def wrap_json_detectors_results(success, error_message, results=None):
"""
Wrap the detector results.
:param success:
:param error_message:
:param results:
:return:
"""
results_json = {}
if results:
results_json['detectors'] = results
return {
"success": success,
"error": error_message,
"results": results_json
def output_json(filename, error, results):
# Create our encapsulated JSON result.
json_result = {
"success": error is None,
"error": error,
"results": results
}
def output_json(results, filename):
json_result = wrap_json_detectors_results(True, None, results)
# Determine if we should output to stdout
if filename is None:
# Write json to console
print(json.dumps(json_result))
@ -271,7 +274,7 @@ def parse_args(detector_classes, printer_classes):
group_detector = parser.add_argument_group('Detectors')
group_printer = parser.add_argument_group('Printers')
group_misc = parser.add_argument_group('Additional option')
group_misc = parser.add_argument_group('Additional options')
group_detector.add_argument('--detect',
help='Comma-separated list of detectors, defaults to all, '
@ -337,12 +340,17 @@ def parse_args(detector_classes, printer_classes):
action='store_true',
default=defaults_flag_in_config['exclude_high'])
group_misc.add_argument('--json',
help='Export the results as a JSON file ("--json -" to export to stdout)',
action='store',
default=defaults_flag_in_config['json'])
group_misc.add_argument('--json-types',
help='Comma-separated list of result types to output to JSON, defaults to all, '
'available types: {}'.format(
', '.join(output_type for output_type in JSON_OUTPUT_TYPES)),
action='store',
default=defaults_flag_in_config['json-types'])
group_misc.add_argument('--disable-color',
help='Disable output colorization',
@ -383,7 +391,6 @@ def parse_args(detector_classes, printer_classes):
action=OutputMarkdown,
default=False)
group_misc.add_argument('--checklist',
help=argparse.SUPPRESS,
action='store_true',
@ -423,6 +430,14 @@ def parse_args(detector_classes, printer_classes):
args = parser.parse_args()
read_config_file(args)
args.filter_paths = parse_filter_paths(args)
# Verify our json-type output is valid
args.json_types = set(args.json_types.split(','))
for json_type in args.json_types:
if json_type not in JSON_OUTPUT_TYPES:
raise Exception(f"Error: \"{json_type}\" is not a valid JSON result output type.")
return args
class ListDetectors(argparse.Action):
@ -434,7 +449,8 @@ class ListDetectors(argparse.Action):
class ListDetectorsJson(argparse.Action):
def __call__(self, parser, *args, **kwargs):
detectors, _ = get_detectors_and_printers()
output_detectors_json(detectors)
detector_types_json = output_detectors_json(detectors)
print(json.dumps(detector_types_json))
parser.exit()
class ListPrinters(argparse.Action):
@ -501,10 +517,16 @@ def main_impl(all_detector_classes, all_printer_classes):
# Set colorization option
set_colorization_enabled(not args.disable_color)
# If we are outputting json to stdout, we'll want to disable any logging.
stdout_json = args.json == "-"
if stdout_json:
logging.disable(logging.CRITICAL)
# Define some variables for potential JSON output
json_results = {}
output_error = None
outputting_json = args.json is not None
outputting_json_stdout = args.json == '-'
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output.
if outputting_json:
StandardOutputCapture.enable(outputting_json_stdout)
printer_classes = choose_printers(args, all_printer_classes)
detector_classes = choose_detectors(args, all_detector_classes)
@ -540,33 +562,56 @@ def main_impl(all_detector_classes, all_printer_classes):
try:
filename = args.filename
# Determine if we are handling ast from solc
if args.solc_ast or (filename.endswith('.json') and not is_supported(filename)):
globbed_filenames = glob.glob(filename, recursive=True)
if os.path.isfile(filename) or is_supported(filename):
(results, number_contracts) = process(filename, args, detector_classes, printer_classes)
elif os.path.isdir(filename) or len(globbed_filenames) > 0:
extension = "*.sol" if not args.solc_ast else "*.json"
filenames = glob.glob(os.path.join(filename, extension))
filenames = glob.glob(os.path.join(filename, "*.json"))
if not filenames:
filenames = globbed_filenames
number_contracts = 0
results = []
if args.splitted and args.solc_ast:
(results, number_contracts) = process_files(filenames, args, detector_classes, printer_classes)
slither_instances = []
if args.splitted:
(slither_instance, results, number_contracts) = process_from_asts(filenames, args, detector_classes, printer_classes)
slither_instances.append(slither_instance)
else:
for filename in filenames:
(results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes)
(slither_instance, results_tmp, number_contracts_tmp) = process_single(filename, args, detector_classes, printer_classes)
number_contracts += number_contracts_tmp
results += results_tmp
slither_instances.append(slither_instance)
# Rely on CryticCompile to discern the underlying type of compilations.
else:
raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename))
(slither_instances, results, number_contracts) = process_all(filename, args, detector_classes, printer_classes)
# Determine if we are outputting JSON
if outputting_json:
# Add our compilation information to JSON
if 'compilations' in args.json_types:
compilation_results = []
for slither_instance in slither_instances:
compilation_results.append(generate_standard_export(slither_instance.crytic_compile))
json_results['compilations'] = compilation_results
# Add our detector results to JSON if desired.
if results and 'detectors' in args.json_types:
json_results['detectors'] = results
# Add our detector types to JSON
if 'list-detectors' in args.json_types:
detectors, _ = get_detectors_and_printers()
json_results['list-detectors'] = output_detectors_json(detectors)
if args.json:
output_json(results, None if stdout_json else args.json)
# Add our detector types to JSON
if 'list-printers' in args.json_types:
_, printers = get_detectors_and_printers()
json_results['list-printers'] = output_printers_json(printers)
# Output our results to markdown if we wish to compile a checklist.
if args.checklist:
output_results_to_markdown(results)
# Dont print the number of result for printers
if number_contracts == 0:
logger.warn(red('No contract was analyzed'))
@ -576,27 +621,33 @@ def main_impl(all_detector_classes, all_printer_classes):
logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results))
if args.ignore_return_value:
return
exit(results)
except SlitherException as se:
# Output our error accordingly, via JSON or logging.
if stdout_json:
print(json.dumps(wrap_json_detectors_results(False, str(se), [])))
else:
output_error = str(se)
logging.error(red('Error:'))
logging.error(red(se))
logging.error(red(output_error))
logging.error('Please report an issue to https://github.com/crytic/slither/issues')
sys.exit(-1)
except Exception:
# Output our error accordingly, via JSON or logging.
if stdout_json:
print(json.dumps(wrap_json_detectors_results(False, traceback.format_exc(), [])))
else:
output_error = traceback.format_exc()
logging.error('Error in %s' % args.filename)
logging.error(traceback.format_exc())
sys.exit(-1)
logging.error(output_error)
# If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON.
if outputting_json:
if 'console' in args.json_types:
json_results['console'] = {
'stdout': StandardOutputCapture.get_stdout_output(),
'stderr': StandardOutputCapture.get_stderr_output()
}
StandardOutputCapture.disable()
output_json(None if outputting_json_stdout else args.json, output_error, json_results)
# Exit with the appropriate status code
if output_error:
sys.exit(-1)
else:
exit(results)
if __name__ == '__main__':

@ -64,7 +64,7 @@ class NodeType:
# Node not related to the CFG
# Use for state variable declaration, or modifier calls
STANDALONE = 0x50
OTHER_ENTRYPOINT = 0x50
# @staticmethod
@ -111,6 +111,23 @@ def link_nodes(n1, n2):
n1.add_son(n2)
n2.add_father(n1)
def recheable(node):
'''
Return the set of nodes reacheable from the node
:param node:
:return: set(Node)
'''
nodes = node.sons
visited = set()
while nodes:
next = nodes[0]
nodes = nodes[1:]
if not next in visited:
visited.add(next)
for son in next.sons:
if not son in visited:
nodes.append(son)
return visited
# endregion
@ -183,6 +200,10 @@ class Node(SourceMapping, ChildFunction):
self._expression_vars_read = []
self._expression_calls = []
# Computed on the fly, can be True of False
self._can_reenter = None
self._can_send_eth = None
###################################################################################
###################################################################################
# region General's properties
@ -385,6 +406,39 @@ class Node(SourceMapping, ChildFunction):
def calls_as_expression(self):
return list(self._expression_calls)
def can_reenter(self, callstack=None):
'''
Check if the node can re-enter
Do not consider CREATE as potential re-enter, but check if the
destination's constructor can contain a call (recurs. follow nested CREATE)
For Solidity > 0.5, filter access to public variables and constant/pure/view
For call to this. check if the destination can re-enter
Do not consider Send/Transfer as there is not enough gas
:param callstack: used internally to check for recursion
:return bool:
'''
from slither.slithir.operations import Call
if self._can_reenter is None:
self._can_reenter = False
for ir in self.irs:
if isinstance(ir, Call) and ir.can_reenter(callstack):
self._can_reenter = True
return True
return self._can_reenter
def can_send_eth(self):
'''
Check if the node can send eth
:return bool:
'''
from slither.slithir.operations import Call
if self._can_send_eth is None:
for ir in self.all_slithir_operations():
if isinstance(ir, Call) and ir.can_send_eth():
self._can_send_eth = True
return True
return self._can_reenter
# endregion
###################################################################################
###################################################################################

@ -18,3 +18,7 @@ class ChildNode(object):
@property
def contract(self):
return self.node.function.contract
@property
def slither(self):
return self.contract.slither

@ -22,7 +22,33 @@ logger = logging.getLogger("Function")
ReacheableNode = namedtuple('ReacheableNode', ['node', 'ir'])
ModifierStatements = namedtuple('Modifier', ['modifier', 'node'])
class ModifierStatements:
def __init__(self, modifier, entry_point, nodes):
self._modifier = modifier
self._entry_point = entry_point
self._nodes = nodes
@property
def modifier(self):
return self._modifier
@property
def entry_point(self):
return self._entry_point
@entry_point.setter
def entry_point(self, entry_point):
self._entry_point = entry_point
@property
def nodes(self):
return self._nodes
@nodes.setter
def nodes(self, nodes):
self._nodes = nodes
class FunctionType(Enum):
NORMAL = 0
@ -106,6 +132,10 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
self._function_type = None
self._is_constructor = None
# Computed on the fly, can be True of False
self._can_reenter = None
self._can_send_eth = None
###################################################################################
###################################################################################
# region General properties
@ -117,7 +147,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
"""
str: function name
"""
if self._function_type == FunctionType.CONSTRUCTOR:
if self._name == '' and self._function_type == FunctionType.CONSTRUCTOR:
return 'constructor'
elif self._function_type == FunctionType.FALLBACK:
return 'fallback'
@ -143,11 +173,44 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
name, parameters, _ = self.signature
return self.contract_declarer.name + '.' + name + '(' + ','.join(parameters) + ')'
@property
def contains_assembly(self):
return self._contains_assembly
def can_reenter(self, callstack=None):
'''
Check if the function can re-enter
Follow internal calls.
Do not consider CREATE as potential re-enter, but check if the
destination's constructor can contain a call (recurs. follow nested CREATE)
For Solidity > 0.5, filter access to public variables and constant/pure/view
For call to this. check if the destination can re-enter
Do not consider Send/Transfer as there is not enough gas
:param callstack: used internally to check for recursion
:return bool:
'''
from slither.slithir.operations import Call
if self._can_reenter is None:
self._can_reenter = False
for ir in self.all_slithir_operations():
if isinstance(ir, Call) and ir.can_reenter(callstack):
self._can_reenter = True
return True
return self._can_reenter
def can_send_eth(self):
'''
Check if the function can send eth
:return bool:
'''
from slither.slithir.operations import Call
if self._can_send_eth is None:
for ir in self.all_slithir_operations():
if isinstance(ir, Call) and ir.can_send_eth():
self._can_send_eth = True
return True
return self._can_reenter
@property
def slither(self):
return self.contract.slither
@ -403,7 +466,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
"""
# This is a list of contracts internally, so we convert it to a list of constructor functions.
return [c for c in self._explicit_base_constructor_calls if c.modifier.constructors_declared]
return list(self._explicit_base_constructor_calls)
# endregion
@ -1158,8 +1221,6 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
external_calls_as_expressions = [item for sublist in external_calls_as_expressions for item in sublist]
self._external_calls_as_expressions = list(set(external_calls_as_expressions))
# endregion
###################################################################################
###################################################################################
@ -1266,10 +1327,12 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
node.slithir_generation()
for modifier_statement in self.modifiers_statements:
modifier_statement.node.slithir_generation()
for node in modifier_statement.nodes:
node.slithir_generation()
for modifier_statement in self.explicit_base_constructor_calls_statements:
modifier_statement.node.slithir_generation()
for node in modifier_statement.nodes:
node.slithir_generation()
self._analyze_read_write()
self._analyze_calls()

@ -1,11 +1,13 @@
from slither.core.expressions.expression import Expression
from slither.utils.arithmetic import convert_subdenomination
class Literal(Expression):
def __init__(self, value, type):
def __init__(self, value, type, subdenomination=None):
super(Literal, self).__init__()
self._value = value
self._type = type
self._subdenomination = subdenomination
@property
def value(self):
@ -15,6 +17,12 @@ class Literal(Expression):
def type(self):
return self._type
@property
def subdenomination(self):
return self._subdenomination
def __str__(self):
if self.subdenomination:
return str(convert_subdenomination(self._value, self.subdenomination))
# be sure to handle any character
return str(self._value)

@ -61,6 +61,9 @@ class Slither(Context):
:param path:
:return:
"""
if self.crytic_compile and path in self.crytic_compile.src_content:
self.source_code[path] = self.crytic_compile.src_content[path]
else:
with open(path, encoding='utf8', newline='') as f:
self.source_code[path] = f.read()
@ -74,6 +77,8 @@ class Slither(Context):
@property
def solc_version(self):
"""str: Solidity version."""
if self.crytic_compile:
return self.crytic_compile.compiler_version.version
return self._solc_version
@property

@ -90,18 +90,23 @@ class SourceMapping(Context):
is_dependency = slither.crytic_compile.is_dependency(filename_absolute)
if filename_absolute in slither.source_code:
if filename_absolute in slither.source_code or filename_absolute in slither.crytic_compile.src_content:
filename = filename_absolute
elif filename_relative in slither.source_code:
filename = filename_relative
elif filename_short in slither.source_code:
filename = filename_short
else:#
filename = filename_used.used
else:
filename = filename_used
else:
filename = filename_used
if filename in slither.source_code:
if slither.crytic_compile and filename in slither.crytic_compile.src_content:
source_code = slither.crytic_compile.src_content[filename]
(lines, starting_column, ending_column) = SourceMapping._compute_line(source_code,
s,
l)
elif filename in slither.source_code:
source_code = slither.source_code[filename]
(lines, starting_column, ending_column) = SourceMapping._compute_line(source_code,
s,

@ -36,5 +36,6 @@ from .source.rtlo import RightToLeftOverride
from .statements.too_many_digits import TooManyDigits
from .operations.unchecked_low_level_return_values import UncheckedLowLevel
from .operations.unchecked_send_return_value import UncheckedSend
from .operations.void_constructor import VoidConstructor
#
#

@ -10,6 +10,7 @@ class NamingConvention(AbstractDetector):
Exceptions:
- Allow constant variables name/symbol/decimals to be lowercase (ERC20)
- Allow '_' at the beggining of the mixed_case match for private variables and unused parameters
- Ignore echidna properties (functions with names starting 'echidna_' or 'crytic_'
"""
ARGUMENT = 'naming-convention'
@ -92,9 +93,13 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
results.append(json)
for func in contract.functions_declared:
if func.is_constructor:
continue
if not self.is_mixed_case(func.name):
if func.visibility in ['internal', 'private'] and self.is_mixed_case_with_underscore(func.name):
continue
if func.name.startswith("echidna_") or func.name.startswith("crytic_"):
continue
info = "Function '{}' ({}) is not in mixedCase\n"
info = info.format(func.canonical_name, func.source_mapping_str)

@ -0,0 +1,46 @@
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import Nop
class VoidConstructor(AbstractDetector):
ARGUMENT = 'void-cst'
HELP = 'Constructor called not implemented'
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor'
WIKI_TITLE = 'Void Constructor'
WIKI_DESCRIPTION = 'Detect the call to a constructor not implemented'
WIKI_RECOMMENDATION = 'Remove the constructor call.'
WIKI_EXPLOIT_SCENARIO = '''
```solidity
contract A{}
contract B is A{
constructor() public A(){}
}
```
By reading B's constructor definition, the reader might assume that `A()` initiate the contract, while no code is executed.'''
def _detect(self):
"""
"""
results = []
for c in self.contracts:
cst = c.constructor
if cst:
for constructor_call in cst.explicit_base_constructor_calls_statements:
for node in constructor_call.nodes:
if any(isinstance(ir, Nop) for ir in node.irs):
info = "Void constructor called in {} ({}):\n"
info = info.format(cst.canonical_name, cst.source_mapping_str)
info += "\t-{} {}\n".format(str(node.expression), node.source_mapping_str)
json = self.generate_json_result(info)
self.add_function_to_json(cst, json)
self.add_nodes_to_json([node], json)
results.append(json)
return results

@ -11,9 +11,8 @@ from slither.core.expressions import UnaryOperation, UnaryOperationType
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.slithir.operations import (HighLevelCall, LowLevelCall,
LibraryCall,
Call,
Send, Transfer)
from slither.core.variables.variable import Variable
def union_dict(d1, d2):
d3 = {k: d1.get(k, set()) | d2.get(k, set()) for k in set(list(d1.keys()) + list(d2.keys()))}
@ -25,12 +24,6 @@ def dict_are_equal(d1, d2):
return all(set(d1[k]) == set(d2[k]) for k in d1.keys())
class Reentrancy(AbstractDetector):
# This detector is not meant to be registered
# It is inherited by reentrancy variantsœ
# ARGUMENT = 'reentrancy'
# HELP = 'Reentrancy vulnerabilities'
# IMPACT = DetectorClassification.HIGH
# CONFIDENCE = DetectorClassification.HIGH
KEY = 'REENTRANCY'
@ -43,27 +36,10 @@ class Reentrancy(AbstractDetector):
- low level call
- high level call
Do not consider Send/Transfer as there is not enough gas
"""
for ir in irs:
if isinstance(ir, LowLevelCall):
return True
if isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall):
# If solidity >0.5, STATICCALL is used
if self.slither.solc_version and self.slither.solc_version.startswith('0.5.'):
if isinstance(ir.function, Function) and (ir.function.view or ir.function.pure):
continue
if isinstance(ir.function, Variable):
continue
# If there is a call to itself
# We can check that the function called is
# reentrancy-safe
if ir.destination == SolidityVariable('this'):
if isinstance(ir.function, Variable):
continue
if not ir.function.all_high_level_calls():
if not ir.function.all_low_level_calls():
continue
if isinstance(ir, Call) and ir.can_reenter():
return True
return False
@ -73,9 +49,11 @@ class Reentrancy(AbstractDetector):
Detect if the node can send eth
"""
for ir in irs:
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)):
if ir.call_value:
if isinstance(ir, Call) and ir.can_send_eth():
return True
# if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)):
# if ir.call_value:
# return True
return False
def _filter_if(self, node):
@ -174,7 +152,6 @@ class Reentrancy(AbstractDetector):
self._explore(son, visited, node)
sons = [sons[0]]
for son in sons:
self._explore(son, visited)

@ -48,7 +48,6 @@ Use:
if isinstance(read, Constant):
# read.value can return an int or a str. Convert it to str
value_as_str = read.original_value
line_of_code = str(node.expression)
if '00000' in value_as_str:
# Info to be printed
ret.append(node)

@ -13,3 +13,4 @@ from .summary.variable_order import VariableOrder
from .summary.data_depenency import DataDependency
from .summary.modifier_calls import Modifiers
from .summary.require_calls import RequireOrAssert
from .summary.constructor_calls import ConstructorPrinter

@ -0,0 +1,47 @@
"""
Module printing summary of the contract
"""
from slither.printers.abstract_printer import AbstractPrinter
class ConstructorPrinter(AbstractPrinter):
WIKI = 'https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls'
ARGUMENT = 'constructor-calls'
HELP = 'Print the constructors executed'
def _get_soruce_code(self,cst):
src_mapping = cst.source_mapping
content= self.slither.source_code[src_mapping['filename_absolute']]
start = src_mapping['start']
end = src_mapping['start'] + src_mapping['length']
initial_space = src_mapping['starting_column']
return ' ' * initial_space + content[start:end]
def output(self,_filename):
for contract in self.contracts:
stack_name = []
stack_definition = []
print("\n\nContact Name:",contract.name)
print(" Constructor Call Sequence: ", sep=' ', end='', flush=True)
cst = contract.constructors_declared
if cst:
stack_name.append(contract.name)
stack_definition.append(self._get_soruce_code(cst))
for inherited_contract in contract.inheritance:
cst = inherited_contract.constructors_declared
if cst:
stack_name.append(inherited_contract.name)
stack_definition.append(self._get_soruce_code(cst))
if len(stack_name)>0:
print(" ",stack_name[len(stack_name)-1], sep=' ', end='', flush=True)
count = len(stack_name)-2;
while count>=0:
print("-->",stack_name[count], sep=' ', end='', flush=True)
count= count-1;
print("\n Constructor Definitions:")
count = len(stack_definition)-1
while count>=0:
print("\n Contract name:", stack_name[count])
print ("\n", stack_definition[count])
count = count-1;

@ -35,13 +35,9 @@ class PrinterSlithIR(AbstractPrinter):
for ir in node.irs:
print('\t\t\t{}'.format(ir))
for modifier_statement in function.modifiers_statements:
print(f'\t\tModifier Call {modifier_statement.node.expression}')
for ir in modifier_statement.node.irs:
print('\t\t\t{}'.format(ir))
print(f'\t\tModifier Call {modifier_statement.entry_point.expression}')
for modifier_statement in function.explicit_base_constructor_calls_statements:
print(f'\t\tConstructor Call {modifier_statement.node.expression}')
for ir in modifier_statement.node.irs:
print('\t\t\t{}'.format(ir))
print(f'\t\tConstructor Call {modifier_statement.entry_point.expression}')
for modifier in contract.modifiers:
print('\tModifier {}'.format(modifier.canonical_name))
for node in modifier.nodes:

@ -22,14 +22,14 @@ logger_printer = logging.getLogger("Printers")
class Slither(SlitherSolc):
def __init__(self, contract, **kwargs):
def __init__(self, target, **kwargs):
'''
Args:
contract (str| list(json))
target (str | list(json) | CryticCompile)
Keyword Args:
solc (str): solc binary location (default 'solc')
disable_solc_warnings (bool): True to disable solc warnings (default false)
solc_argeuments (str): solc arguments (default '')
solc_arguments (str): solc arguments (default '')
ast_format (str): ast format (default '--ast-compact-json')
filter_paths (list(str)): list of path to filter (default [])
triage_mode (bool): if true, switch to triage mode (default false)
@ -46,14 +46,17 @@ class Slither(SlitherSolc):
'''
# list of files provided (see --splitted option)
if isinstance(contract, list):
self._init_from_list(contract)
elif contract.endswith('.json'):
self._init_from_raw_json(contract)
if isinstance(target, list):
self._init_from_list(target)
elif isinstance(target, str) and target.endswith('.json'):
self._init_from_raw_json(target)
else:
super(Slither, self).__init__('')
try:
crytic_compile = CryticCompile(contract, **kwargs)
if isinstance(target, CryticCompile):
crytic_compile = target
else:
crytic_compile = CryticCompile(target, **kwargs)
self._crytic_compile = crytic_compile
except InvalidCompilation as e:
raise SlitherError('Invalid compilation: \n'+str(e))

@ -21,7 +21,7 @@ from slither.slithir.operations import (Assignment, Balance, Binary,
NewElementaryType, NewStructure,
OperationWithLValue, Push, Return,
Send, SolidityCall, Transfer,
TypeConversion, Unary, Unpack)
TypeConversion, Unary, Unpack, Nop)
from slither.slithir.tmp_operations.argument import Argument, ArgumentType
from slither.slithir.tmp_operations.tmp_call import TmpCall
from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray
@ -621,6 +621,9 @@ def extract_tmp_call(ins, contract):
return e
if isinstance(ins.called, Contract):
# Called a base constructor, where there is no constructor
if ins.called.constructor is None:
return Nop()
internalcall = InternalCall(ins.called.constructor, ins.nbr_arguments, ins.lvalue,
ins.type_call)
internalcall.call_id = ins.call_id

@ -30,3 +30,4 @@ from .length import Length
from .balance import Balance
from .phi import Phi
from .phi_callback import PhiCallback
from .nop import Nop

@ -15,3 +15,16 @@ class Call(Operation):
def arguments(self, v):
self._arguments = v
def can_reenter(self, callstack=None):
'''
Must be called after slithIR analysis pass
:return: bool
'''
return False
def can_send_eth(self):
'''
Must be called after slithIR analysis pass
:return: bool
'''
return False

@ -2,6 +2,7 @@ from slither.slithir.operations.call import Call
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.core.variables.variable import Variable
from slither.core.declarations.solidity_variables import SolidityVariable
from slither.core.declarations.function import Function
from slither.slithir.utils.utils import is_valid_lvalue
from slither.slithir.variables.constant import Constant
@ -86,6 +87,55 @@ class HighLevelCall(Call, OperationWithLValue):
def type_call(self):
return self._type_call
###################################################################################
###################################################################################
# region Analyses
###################################################################################
###################################################################################
def can_reenter(self, callstack=None):
'''
Must be called after slithIR analysis pass
For Solidity > 0.5, filter access to public variables and constant/pure/view
For call to this. check if the destination can re-enter
:param callstack: check for recursion
:return: bool
'''
# If solidity >0.5, STATICCALL is used
if self.slither.solc_version and self.slither.solc_version.startswith('0.5.'):
if isinstance(self.function, Function) and (self.function.view or self.function.pure):
return False
if isinstance(self.function, Variable):
return False
# If there is a call to itself
# We can check that the function called is
# reentrancy-safe
if self.destination == SolidityVariable('this'):
if isinstance(self.function, Variable):
return False
# In case of recursion, return False
callstack = [] if callstack is None else callstack
if self.function in callstack:
return False
callstack = callstack + [self.function]
if self.function.can_reenter(callstack):
return True
return True
def can_send_eth(self):
'''
Must be called after slithIR analysis pass
:return: bool
'''
return self._call_value is not None
# endregion
###################################################################################
###################################################################################
# region Built in
###################################################################################
###################################################################################
def __str__(self):
value = ''
gas = ''

@ -9,6 +9,18 @@ class LibraryCall(HighLevelCall):
def _check_destination(self, destination):
assert isinstance(destination, (Contract))
def can_reenter(self, callstack=None):
'''
Must be called after slithIR analysis pass
:return: bool
'''
# In case of recursion, return False
callstack = [] if callstack is None else callstack
if self.function in callstack:
return False
callstack = callstack + [self.function]
return self.function.can_reenter(callstack)
def __str__(self):
gas = ''
if self.call_gas:

@ -54,6 +54,20 @@ class LowLevelCall(Call, OperationWithLValue):
# remove None
return self._unroll([x for x in all_read if x])
def can_reenter(self, callstack=None):
'''
Must be called after slithIR analysis pass
:return: bool
'''
return True
def can_send_eth(self):
'''
Must be called after slithIR analysis pass
:return: bool
'''
return self._call_value is not None
@property
def destination(self):
return self._destination

@ -31,16 +31,52 @@ class NewContract(Call, OperationWithLValue):
def call_id(self, c):
self._callid = c
@property
def contract_name(self):
return self._contract_name
@property
def read(self):
return self._unroll(self.arguments)
@property
def contract_created(self):
contract_name = self.contract_name
contract_instance = self.slither.get_contract_from_name(contract_name)
return contract_instance
###################################################################################
###################################################################################
# region Analyses
###################################################################################
###################################################################################
def can_reenter(self, callstack=None):
'''
Must be called after slithIR analysis pass
For Solidity > 0.5, filter access to public variables and constant/pure/view
For call to this. check if the destination can re-enter
:param callstack: check for recursion
:return: bool
'''
callstack = [] if callstack is None else callstack
constructor = self.contract_created.constructor
if constructor is None:
return False
if constructor in callstack:
return False
callstack = callstack + [constructor]
return constructor.can_reenter(callstack)
def can_send_eth(self):
'''
Must be called after slithIR analysis pass
:return: bool
'''
return self._call_value is not None
# endregion
def __str__(self):
value = ''
if self.call_value:

@ -0,0 +1,14 @@
from .operation import Operation
class Nop(Operation):
@property
def read(self):
return []
@property
def used(self):
return []
def __str__(self):
return "NOP"

@ -17,6 +17,9 @@ class Send(Call, OperationWithLValue):
self._call_value = value
def can_send_eth(self):
return True
@property
def call_value(self):
return self._call_value

@ -11,6 +11,8 @@ class Transfer(Call):
self._call_value = value
def can_send_eth(self):
return True
@property
def call_value(self):

@ -1,14 +1,18 @@
from .variable import SlithIRVariable
from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint
from slither.utils.arithmetic import convert_subdenomination
class Constant(SlithIRVariable):
def __init__(self, val, type=None):
def __init__(self, val, type=None, subdenomination=None):
super(Constant, self).__init__()
assert isinstance(val, str)
self._original_value = val
self._subdenomination = subdenomination
if subdenomination:
val = str(convert_subdenomination(val, subdenomination))
if type:
assert isinstance(type, ElementaryType)

@ -20,7 +20,7 @@ class ContractSolc04(Contract):
def __init__(self, slitherSolc, data):
assert slitherSolc.solc_version.startswith('0.4')
#assert slitherSolc.solc_version.startswith('0.4')
super(ContractSolc04, self).__init__()
self.set_slither(slitherSolc)
@ -373,7 +373,7 @@ class ContractSolc04(Contract):
def _create_node(self, func, counter, variable):
# Function uses to create node for state variable declaration statements
node = Node(NodeType.STANDALONE, counter)
node = Node(NodeType.OTHER_ENTRYPOINT, counter)
node.set_offset(variable.source_mapping, self.slither)
node.set_function(func)
func.add_node(node)
@ -387,17 +387,17 @@ class ContractSolc04(Contract):
def add_constructor_variables(self):
if self.state_variables:
found_candidate = False
for (idx, variable_candidate) in enumerate(self.state_variables):
if variable_candidate.expression and not variable_candidate.is_constant:
found_candidate = True
break
if found_candidate:
constructor_variable = Function()
constructor_variable.set_function_type(FunctionType.CONSTRUCTOR_VARIABLES)
constructor_variable.set_contract(self)
constructor_variable.set_contract_declarer(self)
constructor_variable.set_visibility('internal')
# For now, source mapping of the constructor variable is the whole contract
# Could be improved with a targeted source mapping
constructor_variable.set_offset(self.source_mapping, self.slither)
self._functions[constructor_variable.canonical_name] = constructor_variable
prev_node = self._create_node(constructor_variable, 0, variable_candidate)
@ -409,6 +409,8 @@ class ContractSolc04(Contract):
next_node.add_father(prev_node)
counter += 1
break

@ -2,7 +2,7 @@
"""
import logging
from slither.core.cfg.node import NodeType, link_nodes
from slither.core.cfg.node import NodeType, link_nodes, recheable
from slither.core.declarations.contract import Contract
from slither.core.declarations.function import Function, ModifierStatements, FunctionType
@ -98,6 +98,7 @@ class FunctionSolc(Function):
# This is done to prevent collision during SSA translation
# Use of while in case of collision
# In the worst case, the name will be really long
if local_var.name:
while local_var.name in self._variables:
local_var.name += "_scope_{}".format(self._counter_scope_local_variables)
self._counter_scope_local_variables += 1
@ -219,13 +220,13 @@ class FunctionSolc(Function):
for node in self.nodes:
node.analyze_expressions(self)
if self._filter_ternary():
for modifier_statement in self.modifiers_statements:
modifier_statement.node.analyze_expressions(self)
modifier_statement.nodes = recheable(modifier_statement.entry_point)
for modifier_statement in self.explicit_base_constructor_calls_statements:
modifier_statement.node.analyze_expressions(self)
modifier_statement.nodes = recheable(modifier_statement.entry_point)
self._filter_ternary()
self._remove_alone_endif()
@ -902,15 +903,21 @@ class FunctionSolc(Function):
self._expression_modifiers.append(m)
for m in ExportValues(m).result():
if isinstance(m, Function):
node = self._new_node(NodeType.STANDALONE, modifier['src'])
entry_point = self._new_node(NodeType.OTHER_ENTRYPOINT, modifier['src'])
node = self._new_node(NodeType.EXPRESSION, modifier['src'])
node.add_unparsed_expression(modifier)
link_nodes(entry_point, node)
self._modifiers.append(ModifierStatements(modifier=m,
node=node))
entry_point=entry_point,
nodes=[entry_point, node]))
elif isinstance(m, Contract):
node = self._new_node(NodeType.STANDALONE, modifier['src'])
entry_point = self._new_node(NodeType.OTHER_ENTRYPOINT, modifier['src'])
node = self._new_node(NodeType.EXPRESSION, modifier['src'])
node.add_unparsed_expression(modifier)
link_nodes(entry_point, node)
self._explicit_base_constructor_calls.append(ModifierStatements(modifier=m,
node=node))
entry_point=entry_point,
nodes=[entry_point, node]))
# endregion
###################################################################################
@ -970,9 +977,10 @@ class FunctionSolc(Function):
def _filter_ternary(self):
ternary_found = True
updated = False
while ternary_found:
ternary_found = False
for node in self.nodes:
for node in self._nodes:
has_cond = HasConditional(node.expression)
if has_cond.result():
st = SplitTernaryExpression(node.expression)
@ -981,11 +989,13 @@ class FunctionSolc(Function):
raise ParsingError(f'Incorrect ternary conversion {node.expression} {node.source_mapping_str}')
true_expr = st.true_expression
false_expr = st.false_expression
self.split_ternary_node(node, condition, true_expr, false_expr)
self._split_ternary_node(node, condition, true_expr, false_expr)
ternary_found = True
updated = True
break
return updated
def split_ternary_node(self, node, condition, true_expr, false_expr):
def _split_ternary_node(self, node, condition, true_expr, false_expr):
condition_node = self._new_node(NodeType.IF, node.source_mapping)
condition_node.add_expression(condition)
condition_node.analyze_expressions(self)

@ -220,45 +220,7 @@ def filter_name(value):
return value
# endregion
###################################################################################
###################################################################################
# region Conversion
###################################################################################
###################################################################################
def convert_subdenomination(value, sub):
if sub is None:
return value
# to allow 0.1 ether conversion
if value[0:2] == "0x":
value = float(int(value, 16))
else:
value = float(value)
if sub == 'wei':
return int(value)
if sub == 'szabo':
return int(value * int(1e12))
if sub == 'finney':
return int(value * int(1e15))
if sub == 'ether':
return int(value * int(1e18))
if sub == 'seconds':
return int(value)
if sub == 'minutes':
return int(value * 60)
if sub == 'hours':
return int(value * 60 * 60)
if sub == 'days':
return int(value * 60 * 60 * 24)
if sub == 'weeks':
return int(value * 60 * 60 * 24 * 7)
if sub == 'years':
return int(value * 60 * 60 * 24 * 7 * 365)
logger.error('Subdemoniation not found {}'.format(sub))
return int(value)
# endregion
###################################################################################
###################################################################################
# region Parsing
@ -499,14 +461,19 @@ def parse_expression(expression, caller_context):
assignement.set_offset(expression['src'], caller_context.slither)
return assignement
elif name == 'Literal':
subdenomination = None
assert 'children' not in expression
if is_compact_ast:
value = expression['value']
if value:
if 'subdenomination' in expression and expression['subdenomination']:
value = str(convert_subdenomination(value, expression['subdenomination']))
subdenomination = expression['subdenomination']
elif not value and value != "":
value = '0x'+expression['hexValue']
type = expression['typeDescriptions']['typeString']
@ -519,7 +486,7 @@ def parse_expression(expression, caller_context):
value = expression['attributes']['value']
if value:
if 'subdenomination' in expression['attributes'] and expression['attributes']['subdenomination']:
value = str(convert_subdenomination(value, expression['attributes']['subdenomination']))
subdenomination = expression['attributes']['subdenomination']
elif value is None:
# for literal declared as hex
# see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals
@ -540,7 +507,7 @@ def parse_expression(expression, caller_context):
type = ElementaryType('address')
else:
type = ElementaryType('string')
literal = Literal(value, type)
literal = Literal(value, type, subdenomination)
literal.set_offset(expression['src'], caller_context.slither)
return literal
@ -590,7 +557,7 @@ def parse_expression(expression, caller_context):
# if abi.decode is used
# For example, abi.decode(data, ...(uint[]) )
if right is None:
return _parse_elementary_type_name_expression(left, is_compact_ast, caller_context)
return parse_expression(left, caller_context)
left_expression = parse_expression(left, caller_context)
right_expression = parse_expression(right, caller_context)

@ -104,10 +104,7 @@ class SlitherSolc(Slither):
return
for contract_data in data_loaded[self.get_children()]:
# if self.solc_version == '0.3':
# assert contract_data[self.get_key()] == 'Contract'
# contract = ContractSolc03(self, contract_data)
if self.solc_version == '0.4':
assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective']
if contract_data[self.get_key()] == 'ContractDefinition':
contract = ContractSolc04(self, contract_data)
@ -224,11 +221,11 @@ class SlitherSolc(Slither):
else:
father_constructors.append(self._contracts_by_id[i])
except KeyError:
txt = 'A contract was not found, it is likely that your codebase contains muliple contracts with the same name'
txt += 'Truffle does not handle this case during compilation'
txt += 'Please read https://github.com/trailofbits/slither/wiki#keyerror-or-nonetype-error'
txt += 'And update your code to remove the duplicate'
except KeyError as e:
txt = f'A contract was not found (id {e}), it is likely that your codebase contains muliple contracts with the same name. '
txt += 'Truffle does not handle this case during compilation. '
txt += 'Please read https://github.com/trailofbits/slither/wiki#keyerror-or-nonetype-error, '
txt += 'and update your code to remove the duplicate'
raise ParsingContractNotFound(txt)
contract.setInheritance(ancestors, fathers, father_constructors)

@ -0,0 +1,6 @@
## Demo
This directory contains an example of Slither utility.
See the [utility documentation](https://github.com/crytic/slither/wiki/Adding-a-new-utility)

@ -0,0 +1,38 @@
import os
import argparse
import logging
from slither import Slither
from crytic_compile import cryticparser
logging.basicConfig()
logging.getLogger("Slither").setLevel(logging.INFO)
logger = logging.getLogger("Slither-demo")
def parse_args():
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
"""
parser = argparse.ArgumentParser(description='Demo',
usage='slither-demo filename')
parser.add_argument('filename',
help='The filename of the contract or truffle directory to analyze.')
# Add default arguments from crytic-compile
cryticparser.init(parser)
return parser.parse_args()
def main():
args = parse_args()
# Perform slither analysis on the given filename
slither = Slither(args.filename, **vars(args))
logger.info('Analysis done!')
if __name__ == '__main__':
main()

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save