This is page 8 of 103. Use http://codebase.md/cyfrin/aderyn?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .cargo
│ └── config.toml
├── .git-blame-ignore-revs
├── .gitattributes
├── .github
│ ├── images
│ │ ├── aderyn_logo.png
│ │ ├── poweredbycyfrinblack.png
│ │ └── poweredbycyfrinblue.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── false_positive_issue.md
│ │ └── feature_request.md
│ └── workflows
│ ├── cargo.yml
│ ├── dependencies.yml
│ ├── release.yml
│ ├── reports.yml
│ └── toml.yml
├── .gitignore
├── .gitmodules
├── .vscode
│ └── settings.json
├── aderyn
│ ├── Cargo.toml
│ ├── oranda.json
│ ├── README.md
│ ├── src
│ │ ├── birdsong.rs
│ │ ├── completions.rs
│ │ ├── lib.rs
│ │ ├── lsp.rs
│ │ ├── main.rs
│ │ ├── mcp.rs
│ │ └── panic.rs
│ └── templates
│ └── aderyn.toml
├── aderyn_core
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ ├── ast
│ │ │ ├── ast_nodes.rs
│ │ │ ├── ast.rs
│ │ │ ├── impls
│ │ │ │ ├── ctx
│ │ │ │ │ ├── utils.rs
│ │ │ │ │ └── workspace.rs
│ │ │ │ ├── ctx.rs
│ │ │ │ ├── disp
│ │ │ │ │ ├── blocks.rs
│ │ │ │ │ ├── contracts.rs
│ │ │ │ │ ├── enumerations.rs
│ │ │ │ │ ├── errors.rs
│ │ │ │ │ ├── events.rs
│ │ │ │ │ ├── expressions.rs
│ │ │ │ │ ├── functions.rs
│ │ │ │ │ ├── identifiers.rs
│ │ │ │ │ ├── literals.rs
│ │ │ │ │ ├── modifiers.rs
│ │ │ │ │ ├── statements.rs
│ │ │ │ │ ├── structures.rs
│ │ │ │ │ ├── types.rs
│ │ │ │ │ ├── user_defined_value_types.rs
│ │ │ │ │ ├── using_for_directives.rs
│ │ │ │ │ └── variables.rs
│ │ │ │ ├── disp.rs
│ │ │ │ ├── node
│ │ │ │ │ ├── blocks.rs
│ │ │ │ │ ├── contracts.rs
│ │ │ │ │ ├── documentation.rs
│ │ │ │ │ ├── enumerations.rs
│ │ │ │ │ ├── errors.rs
│ │ │ │ │ ├── events.rs
│ │ │ │ │ ├── expressions.rs
│ │ │ │ │ ├── functions.rs
│ │ │ │ │ ├── identifiers.rs
│ │ │ │ │ ├── import_directives.rs
│ │ │ │ │ ├── literals.rs
│ │ │ │ │ ├── modifiers.rs
│ │ │ │ │ ├── pragma_directives.rs
│ │ │ │ │ ├── source_units.rs
│ │ │ │ │ ├── statements.rs
│ │ │ │ │ ├── structures.rs
│ │ │ │ │ ├── types.rs
│ │ │ │ │ ├── user_defined_value_types.rs
│ │ │ │ │ ├── using_for_directives.rs
│ │ │ │ │ └── variables.rs
│ │ │ │ ├── node.rs
│ │ │ │ ├── own
│ │ │ │ │ ├── hashing.rs
│ │ │ │ │ ├── node_id.rs
│ │ │ │ │ ├── source_units.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ └── own.rs
│ │ │ ├── impls.rs
│ │ │ ├── macros.rs
│ │ │ ├── magic.rs
│ │ │ ├── node_type.rs
│ │ │ └── yul.rs
│ │ ├── ast.rs
│ │ ├── audit
│ │ │ ├── attack_surface.rs
│ │ │ ├── auditor.rs
│ │ │ ├── entrypoint.rs
│ │ │ └── public_functions_no_sender.rs
│ │ ├── audit.rs
│ │ ├── context
│ │ │ ├── browser
│ │ │ │ ├── ancestral_line.rs
│ │ │ │ ├── closest_ancestor.rs
│ │ │ │ ├── external_calls.rs
│ │ │ │ ├── extractor.rs
│ │ │ │ ├── immediate_children.rs
│ │ │ │ ├── location.rs
│ │ │ │ ├── macros.rs
│ │ │ │ ├── parent.rs
│ │ │ │ ├── peek_over.rs
│ │ │ │ ├── peek_under.rs
│ │ │ │ ├── peek.rs
│ │ │ │ ├── siblings.rs
│ │ │ │ ├── sort_nodes.rs
│ │ │ │ └── storage_vars.rs
│ │ │ ├── browser.rs
│ │ │ ├── capturable.rs
│ │ │ ├── flow
│ │ │ │ ├── display.rs
│ │ │ │ ├── error.rs
│ │ │ │ ├── kind.rs
│ │ │ │ ├── primitives.rs
│ │ │ │ ├── reducibles.rs
│ │ │ │ ├── tests.rs
│ │ │ │ ├── utils.rs
│ │ │ │ ├── visualizer.rs
│ │ │ │ └── voids.rs
│ │ │ ├── flow.rs
│ │ │ ├── graph
│ │ │ │ ├── callgraph
│ │ │ │ │ ├── legacy.rs
│ │ │ │ │ ├── new.rs
│ │ │ │ │ ├── tests.rs
│ │ │ │ │ ├── utils.rs
│ │ │ │ │ └── visit.rs
│ │ │ │ ├── callgraph.rs
│ │ │ │ ├── preprocess
│ │ │ │ │ ├── legacy.rs
│ │ │ │ │ └── new.rs
│ │ │ │ ├── preprocess.rs
│ │ │ │ ├── traits.rs
│ │ │ │ └── utils.rs
│ │ │ ├── graph.rs
│ │ │ ├── macros.rs
│ │ │ ├── mcp
│ │ │ │ ├── callgraph
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── callgraph.rs
│ │ │ │ ├── contract_surface
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── util.rs
│ │ │ │ ├── contract_surface.rs
│ │ │ │ ├── list_contracts
│ │ │ │ │ ├── render.rs
│ │ │ │ │ └── tool.rs
│ │ │ │ ├── list_contracts.rs
│ │ │ │ ├── node_finder
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── node_finder.rs
│ │ │ │ ├── node_summarizer
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── node_summarizer.rs
│ │ │ │ ├── project_overview
│ │ │ │ │ ├── render.rs
│ │ │ │ │ └── tool.rs
│ │ │ │ ├── project_overview.rs
│ │ │ │ ├── tool_guide
│ │ │ │ │ └── tool.rs
│ │ │ │ └── tool_guide.rs
│ │ │ ├── mcp.rs
│ │ │ ├── router
│ │ │ │ ├── external_calls.rs
│ │ │ │ ├── internal_calls.rs
│ │ │ │ ├── modifier_calls.rs
│ │ │ │ └── tests.rs
│ │ │ ├── router.rs
│ │ │ └── workspace.rs
│ │ ├── context.rs
│ │ ├── detect
│ │ │ ├── detector.rs
│ │ │ ├── entrypoint.rs
│ │ │ ├── helpers.rs
│ │ │ ├── high
│ │ │ │ ├── _template.rs
│ │ │ │ ├── abi_encode_packed_hash_collision.rs
│ │ │ │ ├── arbitrary_transfer_from.rs
│ │ │ │ ├── const_func_changes_state.rs
│ │ │ │ ├── contract_locks_ether.rs
│ │ │ │ ├── dangerous_unary_operator.rs
│ │ │ │ ├── delegate_call_unchecked_address.rs
│ │ │ │ ├── delete_nested_mapping.rs
│ │ │ │ ├── dynamic_array_length_assignment.rs
│ │ │ │ ├── enumerable_loop_removal.rs
│ │ │ │ ├── eth_send_unchecked_address.rs
│ │ │ │ ├── experimental_encoder.rs
│ │ │ │ ├── function_selector_collision.rs
│ │ │ │ ├── incorrect_caret_operator.rs
│ │ │ │ ├── incorrect_erc20_interface.rs
│ │ │ │ ├── incorrect_erc721_interface.rs
│ │ │ │ ├── incorrect_shift_order.rs
│ │ │ │ ├── misused_boolean.rs
│ │ │ │ ├── msg_value_in_loops.rs
│ │ │ │ ├── multiple_constructors.rs
│ │ │ │ ├── nested_struct_in_mapping.rs
│ │ │ │ ├── out_of_order_retryable.rs
│ │ │ │ ├── pre_declared_variable_usage.rs
│ │ │ │ ├── reentrancy_state_change.rs
│ │ │ │ ├── reused_contract_name.rs
│ │ │ │ ├── rtlo.rs
│ │ │ │ ├── selfdestruct.rs
│ │ │ │ ├── signed_integer_storage_array.rs
│ │ │ │ ├── state_variable_shadowing.rs
│ │ │ │ ├── storage_array_memory_edit.rs
│ │ │ │ ├── strict_equality_contract_balance.rs
│ │ │ │ ├── tautological_compare.rs
│ │ │ │ ├── tautology_or_contradiction.rs
│ │ │ │ ├── tx_origin_used_for_auth.rs
│ │ │ │ ├── unchecked_low_level_call.rs
│ │ │ │ ├── unchecked_send.rs
│ │ │ │ ├── unprotected_initializer.rs
│ │ │ │ ├── unsafe_casting.rs
│ │ │ │ ├── weak_randomness.rs
│ │ │ │ └── yul_return.rs
│ │ │ ├── high.rs
│ │ │ ├── low
│ │ │ │ ├── _template.rs
│ │ │ │ ├── assert_state_change.rs
│ │ │ │ ├── block_timestamp_deadline.rs
│ │ │ │ ├── boolean_equality.rs
│ │ │ │ ├── builtin_symbol_shadowing.rs
│ │ │ │ ├── centralization_risk.rs
│ │ │ │ ├── constant_function_contains_assembly.rs
│ │ │ │ ├── costly_loop.rs
│ │ │ │ ├── dead_code.rs
│ │ │ │ ├── delegatecall_in_loop.rs
│ │ │ │ ├── deprecated_oz_function.rs
│ │ │ │ ├── division_before_multiplication.rs
│ │ │ │ ├── ecrecover.rs
│ │ │ │ ├── empty_block.rs
│ │ │ │ ├── empty_require_revert.rs
│ │ │ │ ├── function_initializing_state.rs
│ │ │ │ ├── function_pointer_in_constructor.rs
│ │ │ │ ├── inconsistent_type_names.rs
│ │ │ │ ├── incorrect_modifier.rs
│ │ │ │ ├── internal_function_used_once.rs
│ │ │ │ ├── large_numeric_literal.rs
│ │ │ │ ├── literal_instead_of_constant.rs
│ │ │ │ ├── local_variable_shadowing.rs
│ │ │ │ ├── missing_inheritance.rs
│ │ │ │ ├── modifier_used_only_once.rs
│ │ │ │ ├── multiple_placeholders.rs
│ │ │ │ ├── non_reentrant_not_first.rs
│ │ │ │ ├── push_0_opcode.rs
│ │ │ │ ├── redundant_statement.rs
│ │ │ │ ├── require_revert_in_loop.rs
│ │ │ │ ├── return_bomb.rs
│ │ │ │ ├── solmate_safe_transfer_lib.rs
│ │ │ │ ├── state_change_without_event.rs
│ │ │ │ ├── state_no_address_check.rs
│ │ │ │ ├── state_variable_could_be_constant.rs
│ │ │ │ ├── state_variable_could_be_immutable.rs
│ │ │ │ ├── state_variable_read_external.rs
│ │ │ │ ├── storage_array_length_not_cached.rs
│ │ │ │ ├── todo.rs
│ │ │ │ ├── unchecked_return.rs
│ │ │ │ ├── uninitialized_local_variable.rs
│ │ │ │ ├── unsafe_erc20_operation.rs
│ │ │ │ ├── unsafe_oz_erc721_mint.rs
│ │ │ │ ├── unspecific_solidity_pragma.rs
│ │ │ │ ├── unused_error.rs
│ │ │ │ ├── unused_import.rs
│ │ │ │ ├── unused_public_function.rs
│ │ │ │ ├── unused_state_variable.rs
│ │ │ │ └── void_constructor.rs
│ │ │ ├── low.rs
│ │ │ └── test_utils.rs
│ │ ├── detect.rs
│ │ ├── lib.rs
│ │ ├── stats
│ │ │ ├── cloc.rs
│ │ │ ├── dbg_tips.txt
│ │ │ ├── ignore.rs
│ │ │ ├── token.rs
│ │ │ └── util.rs
│ │ ├── stats.rs
│ │ ├── test_utils
│ │ │ └── load_source_unit.rs
│ │ ├── test_utils.rs
│ │ ├── visitor
│ │ │ ├── ast_visitor.rs
│ │ │ ├── macros.rs
│ │ │ └── workspace_visitor.rs
│ │ └── visitor.rs
│ ├── templates
│ │ └── mcp-tool-response
│ │ ├── callgraph.md
│ │ ├── contract_surface.md
│ │ ├── list_contracts.md
│ │ ├── node_finder_get_all.md
│ │ ├── node_finder_grep.md
│ │ ├── node_finder_search.md
│ │ ├── node_summarizer.md
│ │ ├── project_overview.md
│ │ └── tool_guide.md
│ └── tests
│ ├── common
│ │ ├── ancestral_line.rs
│ │ ├── closest_ancestor.rs
│ │ ├── immediate_children.rs
│ │ ├── immediate_parent.rs
│ │ ├── mod.rs
│ │ ├── new_ast_nodes.rs
│ │ ├── peek_over.rs
│ │ └── sibling.rs
│ └── traversal.rs
├── aderyn_driver
│ ├── .gitignore
│ ├── benches
│ │ └── detectors.rs
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ ├── compile.rs
│ │ ├── config.rs
│ │ ├── display.rs
│ │ ├── driver.rs
│ │ ├── interface
│ │ │ ├── json.rs
│ │ │ ├── lsp.rs
│ │ │ ├── markdown.rs
│ │ │ ├── mod.rs
│ │ │ ├── sarif.rs
│ │ │ ├── tables.rs
│ │ │ └── util.rs
│ │ ├── lib.rs
│ │ ├── mcp.rs
│ │ ├── process.rs
│ │ └── runner.rs
│ └── tests
│ └── astgen.rs
├── bacon.toml
├── benchmarks
│ ├── aderyn
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── iteration_times.svg
│ │ │ └── pdf.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── iteration_times_small.svg
│ │ ├── iteration_times.svg
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── relative_iteration_times_small.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── SD.svg
│ │ └── typical.svg
│ ├── arbitrary-transfer-from
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── avoid-abi-encode-packed
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── block-timestamp-deadline
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── centralization-risk
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── constants-instead-of-literals
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── delegate-call-in-loop
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── deprecated-oz-functions
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── ecrecover
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── empty-block
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── hello_world
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── inconsistent-type-names
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── large-numeric-literal
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── non-reentrant-before-others
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── push-zero-opcode
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── report
│ │ └── index.html
│ ├── require-with-string
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── solmate-safe-transfer-lib
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unindexed-events
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unprotected-initializer
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unsafe-erc20-functions
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unsafe-oz-erc721-mint
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unspecific-solidity-pragma
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-internal-function
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-modifier
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-public-function
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ └── zero-address-check
│ ├── base
│ │ ├── benchmark.json
│ │ ├── estimates.json
│ │ ├── sample.json
│ │ └── tukey.json
│ ├── change
│ │ └── estimates.json
│ ├── new
│ │ ├── benchmark.json
│ │ ├── estimates.json
│ │ ├── sample.json
│ │ └── tukey.json
│ └── report
│ ├── both
│ │ ├── pdf.svg
│ │ └── regression.svg
│ ├── change
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ └── t-test.svg
│ ├── index.html
│ ├── MAD.svg
│ ├── mean.svg
│ ├── median.svg
│ ├── pdf_small.svg
│ ├── pdf.svg
│ ├── regression_small.svg
│ ├── regression.svg
│ ├── relative_pdf_small.svg
│ ├── relative_regression_small.svg
│ ├── SD.svg
│ ├── slope.svg
│ └── typical.svg
├── Cargo.lock
├── Cargo.toml
├── cli
│ ├── benchmarks.sh
│ └── reportgen.sh
├── CODEOWNERS
├── CONTRIBUTING.md
├── cyfrinup
│ ├── dynamic_script
│ └── why.md
├── deny.toml
├── dist-workspace.toml
├── funding.json
├── LICENSE
├── Makefile
├── package-lock.json
├── package.json
├── README.md
├── RELEASE_CHECKLIST.md
├── reports
│ ├── adhoc-sol-files-highs-only-report.json
│ ├── adhoc-sol-files-report.md
│ ├── ccip-functions-report.md
│ ├── empty_report.md
│ ├── hardhat-playground-report.md
│ ├── nft-report-icm.md
│ ├── nft-report.md
│ ├── prb-math-report.md
│ ├── report.json
│ ├── report.md
│ ├── report.sarif
│ ├── sablier-aderyn-toml-nested-root.md
│ ├── templegold-report.md
│ └── uniswap_profile.md
├── rust-toolchain.toml
├── rustfmt.toml
├── tests
│ ├── adhoc-sol-files
│ │ ├── aderyn.toml
│ │ ├── Counter.sol
│ │ ├── DemoASTNodes.sol
│ │ ├── Helper.sol
│ │ ├── InconsistentUints.sol
│ │ ├── inheritance
│ │ │ ├── ExtendedInheritance.sol
│ │ │ ├── IContractInheritance.sol
│ │ │ └── InheritanceBase.sol
│ │ ├── InternalFunctions.sol
│ │ ├── lib
│ │ │ └── ThisShouldBeExcluded.sol
│ │ ├── multiple-versions
│ │ │ ├── 0.4
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.5
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.6
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.7
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ └── 0.8
│ │ │ ├── A.sol
│ │ │ └── B.sol
│ │ ├── OnceModifierExample.sol
│ │ └── StateVariables.sol
│ ├── ast
│ │ ├── abstract_contract.json
│ │ ├── address_payable.json
│ │ ├── array_type_name.json
│ │ ├── ast-erc4626.json
│ │ ├── base_constructor_call.json
│ │ ├── bit_not.json
│ │ ├── call.json
│ │ ├── constructor.json
│ │ ├── contract_dep_order.json
│ │ ├── do_while.json
│ │ ├── documentation_1.json
│ │ ├── documentation_2.json
│ │ ├── documentation_3.json
│ │ ├── documentation_local_variable.json
│ │ ├── documentation_on_statements.json
│ │ ├── documentation_triple.json
│ │ ├── empty_block.json
│ │ ├── enum_value_declaration.json
│ │ ├── enum_value.json
│ │ ├── event_definition.json
│ │ ├── experimental_encoder_pragma.json
│ │ ├── fallback_and_reveice_ether.json
│ │ ├── fallback_payable.json
│ │ ├── fallback.json
│ │ ├── function_type.json
│ │ ├── function.json
│ │ ├── global_enum.json
│ │ ├── global_struct.json
│ │ ├── inheritance_specifier.json
│ │ ├── leave.json
│ │ ├── license.json
│ │ ├── long_type_name_binary_operation.json
│ │ ├── long_type_name_identifier.json
│ │ ├── loop.json
│ │ ├── mappings.json
│ │ ├── modifier_definition.json
│ │ ├── modifier_invocation.json
│ │ ├── mutability.json
│ │ ├── nested_functions.json
│ │ ├── non_utf8.json
│ │ ├── override.json
│ │ ├── placeholder_statement.json
│ │ ├── receive_ether.json
│ │ ├── short_type_name_ref.json
│ │ ├── short_type_name.json
│ │ ├── slot_offset.json
│ │ ├── smoke.json
│ │ ├── source_location.json
│ │ ├── string.json
│ │ ├── stringlit.json
│ │ ├── switch_default.json
│ │ ├── switch.json
│ │ ├── try_catch.json
│ │ ├── two_base_functions.json
│ │ ├── unicode.json
│ │ ├── used_errors.json
│ │ ├── userDefinedValueType.json
│ │ ├── using_for_directive.json
│ │ ├── var_access.json
│ │ └── yul_hex_literal.json
│ ├── contract-playground
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── dot
│ │ │ └── .gitkeep
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── script
│ │ │ └── Counter.s.sol
│ │ ├── src
│ │ │ ├── AbstractContract.sol
│ │ │ ├── AderynIgnoreCustomDetectors.sol
│ │ │ ├── AdminContract.sol
│ │ │ ├── ArbitraryTransferFrom.sol
│ │ │ ├── AssemblyExample.sol
│ │ │ ├── AssertStateChange.sol
│ │ │ ├── auditor_mode
│ │ │ │ ├── ExternalCalls.sol
│ │ │ │ └── PublicFunctionsWithoutSenderCheck.sol
│ │ │ ├── BooleanEquality.sol
│ │ │ ├── BuiltinSymbolShadow.sol
│ │ │ ├── CacheArrayLength.sol
│ │ │ ├── CallGraphTests.sol
│ │ │ ├── Casting.sol
│ │ │ ├── cloc
│ │ │ │ ├── AnotherHeavilyCommentedContract.sol
│ │ │ │ ├── EmptyContractFile.sol
│ │ │ │ └── HeavilyCommentedContract.sol
│ │ │ ├── CompilerBugStorageSignedIntegerArray.sol
│ │ │ ├── ConstantFuncsAssembly.sol
│ │ │ ├── ConstantsLiterals.sol
│ │ │ ├── ConstFuncChangeState.sol
│ │ │ ├── ContractLocksEther.sol
│ │ │ ├── ContractWithTodo.sol
│ │ │ ├── control_flow
│ │ │ │ └── SimpleProgram.sol
│ │ │ ├── CostlyOperationsInsideLoops.sol
│ │ │ ├── Counter.sol
│ │ │ ├── CrazyPragma.sol
│ │ │ ├── DangerousStrictEquality1.sol
│ │ │ ├── DangerousStrictEquality2.sol
│ │ │ ├── DangerousUnaryOperator.sol
│ │ │ ├── DeadCode.sol
│ │ │ ├── DelegateCallWithoutAddressCheck.sol
│ │ │ ├── DeletionNestedMappingStructureContract.sol
│ │ │ ├── DeprecatedOZFunctions.sol
│ │ │ ├── DivisionBeforeMultiplication.sol
│ │ │ ├── DynamicArrayLengthAssignment.sol
│ │ │ ├── EmitAfterExternalCall.sol
│ │ │ ├── EmptyBlocks.sol
│ │ │ ├── EnumerableSetIteration.sol
│ │ │ ├── eth2
│ │ │ │ └── DepositContract.sol
│ │ │ ├── ExperimentalEncoder.sol
│ │ │ ├── ExternalCalls.sol
│ │ │ ├── FunctionInitializingState.sol
│ │ │ ├── FunctionPointers.sol
│ │ │ ├── FunctionSignatureCollision.sol
│ │ │ ├── HugeConstants.sol
│ │ │ ├── IgnoreEverything.sol
│ │ │ ├── InconsistentUints.sol
│ │ │ ├── IncorrectCaretOperator.sol
│ │ │ ├── IncorrectERC20.sol
│ │ │ ├── IncorrectERC721.sol
│ │ │ ├── IncorrectModifier.sol
│ │ │ ├── IncorrectShift.sol
│ │ │ ├── inheritance
│ │ │ │ ├── ExtendedInheritance.sol
│ │ │ │ ├── IContractInheritance.sol
│ │ │ │ └── InheritanceBase.sol
│ │ │ ├── InternalFunctions.sol
│ │ │ ├── KeccakContract.sol
│ │ │ ├── LocalVariableShadow.sol
│ │ │ ├── MissingInheritance.sol
│ │ │ ├── MisusedBoolean.sol
│ │ │ ├── MsgValueInLoop.sol
│ │ │ ├── MultipleConstructorSchemes.sol
│ │ │ ├── MultiplePlaceholders.sol
│ │ │ ├── nested
│ │ │ │ ├── 1
│ │ │ │ │ └── Nested.sol
│ │ │ │ └── 2
│ │ │ │ └── Nested.sol
│ │ │ ├── nested_mappings
│ │ │ │ ├── LaterVersion.sol
│ │ │ │ └── NestedMappings.sol
│ │ │ ├── OnceModifierExample.sol
│ │ │ ├── OnlyLibrary.sol
│ │ │ ├── OutOfOrderRetryable.sol
│ │ │ ├── parent_chain
│ │ │ │ └── ParentChainContract.sol
│ │ │ ├── PragmaRange.sol
│ │ │ ├── PreDeclaredVarUsage.sol
│ │ │ ├── PublicFunction.sol
│ │ │ ├── PublicVariableReadInExternalContext.sol
│ │ │ ├── RedundantStatements.sol
│ │ │ ├── ReturnBomb.sol
│ │ │ ├── reused_contract_name
│ │ │ │ ├── ContractA.sol
│ │ │ │ └── ContractB.sol
│ │ │ ├── RevertsAndRequriesInLoops.sol
│ │ │ ├── router
│ │ │ │ ├── ExternalCalls.sol
│ │ │ │ ├── FallbackAndReceiveOverrides.sol
│ │ │ │ ├── InternalCalls.sol
│ │ │ │ ├── ModifierCalls.sol
│ │ │ │ └── VarOverridesFunction.sol
│ │ │ ├── RTLO.sol
│ │ │ ├── SendEtherNoChecks.sol
│ │ │ ├── SendEtherNoChecksLibImport.sol
│ │ │ ├── StateChangeAfterExternalCall.sol
│ │ │ ├── StateShadowing.sol
│ │ │ ├── StateVariableCouldBeDeclaredConstant.sol
│ │ │ ├── StateVariableCouldBeDeclaredImmutable.sol
│ │ │ ├── StateVariables.sol
│ │ │ ├── StateVariablesChangesWithoutEvents.sol
│ │ │ ├── StateVariablesManipulation.sol
│ │ │ ├── StorageConditionals.sol
│ │ │ ├── StorageParameters.sol
│ │ │ ├── T11sTranferer.sol
│ │ │ ├── TautologicalCompare.sol
│ │ │ ├── TautologyOrContradiction.sol
│ │ │ ├── TestERC20.sol
│ │ │ ├── TransientKeyword.sol
│ │ │ ├── Trump.sol
│ │ │ ├── TxOriginUsedForAuth.sol
│ │ │ ├── U2.sol
│ │ │ ├── U3.sol
│ │ │ ├── U4.sol
│ │ │ ├── U5.sol
│ │ │ ├── UncheckedCalls.sol
│ │ │ ├── UncheckedReturn.sol
│ │ │ ├── UncheckedSend.sol
│ │ │ ├── UninitializedLocalVariables.sol
│ │ │ ├── UninitializedStateVariable.sol
│ │ │ ├── uniswap
│ │ │ │ ├── UniswapV2Swapper.sol
│ │ │ │ └── UniswapV3Swapper.sol
│ │ │ ├── UnprotectedInitialize.sol
│ │ │ ├── UnsafeERC721Mint.sol
│ │ │ ├── UnusedError.sol
│ │ │ ├── UnusedImport.sol
│ │ │ ├── UnusedStateVariables.sol
│ │ │ ├── UsingSelfdestruct.sol
│ │ │ ├── VoidConstructor.sol
│ │ │ ├── WeakRandomness.sol
│ │ │ ├── WrongOrderOfLayout.sol
│ │ │ ├── YulReturn.sol
│ │ │ └── ZeroAddressCheck.sol
│ │ └── test
│ │ └── Counter.t.sol
│ ├── foundry-nft-f23
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── foundry.lock
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── remappings.txt
│ │ └── src
│ │ ├── BasicNft.sol
│ │ ├── F1.sol
│ │ ├── F2.sol
│ │ ├── Initializer.sol
│ │ └── inner-core-modules
│ │ └── ICM.sol
│ ├── foundry-nft-f23-icm
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── aderyn.toml
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── remappings.txt
│ │ └── src
│ │ ├── BasicNft.sol
│ │ ├── F1.sol
│ │ ├── F2.sol
│ │ ├── Initializer.sol
│ │ └── inner-core-modules
│ │ └── ICM.sol
│ ├── hardhat-js-playground
│ │ ├── .gitignore
│ │ ├── artifacts
│ │ │ ├── build-info
│ │ │ │ └── cee6fe9a9a2f03f7ff10a27ab2746af6.json
│ │ │ └── contracts
│ │ │ ├── Counter.sol
│ │ │ │ ├── Counter.dbg.json
│ │ │ │ └── Counter.json
│ │ │ ├── ExtendedInheritance.sol
│ │ │ │ ├── ExtendedInheritance.dbg.json
│ │ │ │ └── ExtendedInheritance.json
│ │ │ ├── IContractInheritance.sol
│ │ │ │ ├── IContractInheritance.dbg.json
│ │ │ │ └── IContractInheritance.json
│ │ │ ├── InheritanceBase.sol
│ │ │ │ ├── InheritanceBase.dbg.json
│ │ │ │ └── InheritanceBase.json
│ │ │ ├── KeccakContract.sol
│ │ │ │ ├── KeccakContract.dbg.json
│ │ │ │ └── KeccakContract.json
│ │ │ ├── Lock.sol
│ │ │ │ ├── Lock.dbg.json
│ │ │ │ └── Lock.json
│ │ │ └── StateVariables.sol
│ │ │ ├── StateVariables.dbg.json
│ │ │ └── StateVariables.json
│ │ ├── contracts
│ │ │ ├── Counter.sol
│ │ │ ├── ExtendedInheritance.sol
│ │ │ ├── IContractInheritance.sol
│ │ │ ├── InheritanceBase.sol
│ │ │ ├── KeccakContract.sol
│ │ │ ├── Lock.sol
│ │ │ └── StateVariables.sol
│ │ ├── hardhat.config.js
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── scripts
│ │ │ └── deploy.js
│ │ ├── test
│ │ │ └── Lock.js
│ │ └── yarn.lock
│ ├── no-sol-files
│ │ ├── extra
│ │ │ └── HelloAgain.md
│ │ ├── Hello.txt
│ │ └── Hello.yul
│ └── toml
│ ├── nested_project1
│ │ ├── aderyn.toml
│ │ ├── folder1
│ │ │ └── hardhat.config.ts
│ │ ├── folder2
│ │ │ └── hardhat.config.ts
│ │ └── folder3
│ │ └── file.txt
│ └── nested_project2
│ ├── aderyn.toml
│ ├── folder1
│ │ └── foundry.toml
│ └── folder2
│ └── file1.txt
├── tools
│ └── xtask
│ ├── Cargo.toml
│ └── src
│ ├── blesspr.rs
│ ├── cut_release.rs
│ ├── flags.rs
│ ├── main.rs
│ ├── reportgen.rs
│ └── tomlgen.rs
└── typos.toml
```
# Files
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/enumerable_loop_removal.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{NodeID, NodeType};
4 |
5 | use crate::{
6 | capture,
7 | context::{
8 | browser::{ExtractMemberAccesses, GetClosestAncestorOfTypeX},
9 | workspace::WorkspaceContext,
10 | },
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct EnumerableLoopRemovalDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for EnumerableLoopRemovalDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | // Find MemberAccesses with name `remove` and
25 | // typeDescriptions.typeString.contains(EnumerableSet) for each one
26 | // Find the closest ancestor of a loop
27 | // if it exists, extract all `at` member accesses on the enumerableset
28 | // If an `at` memberaccess also exists in the loop, add the remove to found_instances
29 |
30 | context
31 | .member_accesses()
32 | .into_iter()
33 | .filter(|member_access| {
34 | member_access.member_name == "remove"
35 | && member_access
36 | .type_descriptions
37 | .type_string
38 | .as_ref()
39 | .is_some_and(|type_string| type_string.contains("EnumerableSet"))
40 | })
41 | .for_each(|member_access| {
42 | let parent_loops = [
43 | member_access.closest_ancestor_of_type(context, NodeType::ForStatement),
44 | member_access.closest_ancestor_of_type(context, NodeType::WhileStatement),
45 | member_access.closest_ancestor_of_type(context, NodeType::DoWhileStatement),
46 | ];
47 | for parent_loop in parent_loops.into_iter().flatten() {
48 | ExtractMemberAccesses::from(parent_loop).extracted.into_iter().for_each(
49 | |at_member_access| {
50 | if at_member_access.member_name == "at" {
51 | capture!(self, context, member_access);
52 | }
53 | },
54 | );
55 | }
56 | });
57 |
58 | Ok(!self.found_instances.is_empty())
59 | }
60 |
61 | fn severity(&self) -> IssueSeverity {
62 | IssueSeverity::High
63 | }
64 |
65 | fn title(&self) -> String {
66 | String::from("EnumerableSet.remove Corrupts Order")
67 | }
68 |
69 | fn description(&self) -> String {
70 | String::from("If the order of an EnumerableSet is required, removing items in a loop using `at` and `remove` corrupts this order.
71 | Consider using a different data structure or removing items by collecting them during the loop, then removing after the loop.")
72 | }
73 |
74 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
75 | self.found_instances.clone()
76 | }
77 |
78 | fn name(&self) -> String {
79 | format!("{}", IssueDetectorNamePool::EnumerableLoopRemoval)
80 | }
81 | }
82 |
83 | #[cfg(test)]
84 | mod enuemrable_loop_removal_tests {
85 |
86 | use crate::detect::{detector::IssueDetector, high::EnumerableLoopRemovalDetector};
87 |
88 | #[test]
89 |
90 | fn test_enumerable_loop_detector() {
91 | let context = crate::detect::test_utils::load_solidity_source_unit(
92 | "../tests/contract-playground/src/EnumerableSetIteration.sol",
93 | );
94 |
95 | let mut detector = EnumerableLoopRemovalDetector::default();
96 | let found = detector.detect(&context).unwrap();
97 | assert!(found);
98 | assert_eq!(detector.instances().len(), 5);
99 | }
100 | }
101 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/deprecated_oz_function.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::{NodeID, NodeType},
5 | capture,
6 | context::{
7 | browser::GetClosestAncestorOfTypeX,
8 | workspace::{ASTNode, WorkspaceContext},
9 | },
10 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
11 | };
12 | use eyre::Result;
13 |
14 | #[derive(Default)]
15 | pub struct DeprecatedOZFunctionDetector {
16 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
17 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
18 | found_instances: BTreeMap<(String, usize, String), NodeID>,
19 | }
20 |
21 | impl IssueDetector for DeprecatedOZFunctionDetector {
22 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
23 | for identifier in context.identifiers() {
24 | // if source_unit has any ImportDirectives with absolute_path containing "openzeppelin"
25 | // call identifier.accept(self)
26 | if let Some(ASTNode::SourceUnit(source_unit)) =
27 | identifier.closest_ancestor_of_type(context, NodeType::SourceUnit)
28 | {
29 | let import_directives = source_unit.import_directives();
30 | if import_directives.iter().any(|directive| {
31 | directive
32 | .absolute_path
33 | .as_ref()
34 | .is_some_and(|path| path.contains("openzeppelin"))
35 | }) && identifier.name == "_setupRole"
36 | {
37 | capture!(self, context, identifier);
38 | }
39 | } else {
40 | // Optional: handle other cases, or do nothing
41 | }
42 | }
43 | for member_access in context.member_accesses() {
44 | // if source_unit has any ImportDirectives with absolute_path containing "openzeppelin"
45 | // call member_access.accept(self)
46 | if let Some(ASTNode::SourceUnit(source_unit)) =
47 | member_access.closest_ancestor_of_type(context, NodeType::SourceUnit)
48 | {
49 | let import_directives = source_unit.import_directives();
50 | if import_directives.iter().any(|directive| {
51 | directive
52 | .absolute_path
53 | .as_ref()
54 | .is_some_and(|path| path.contains("openzeppelin"))
55 | }) && member_access.member_name == "safeApprove"
56 | {
57 | capture!(self, context, member_access);
58 | }
59 | }
60 | }
61 | Ok(!self.found_instances.is_empty())
62 | }
63 |
64 | fn title(&self) -> String {
65 | String::from("Deprecated OpenZeppelin Function")
66 | }
67 |
68 | fn description(&self) -> String {
69 | String::from(
70 | "Openzeppelin has deprecated several functions and replaced with newer versions. Please consult https://docs.openzeppelin.com/",
71 | )
72 | }
73 |
74 | fn severity(&self) -> IssueSeverity {
75 | IssueSeverity::Low
76 | }
77 |
78 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
79 | self.found_instances.clone()
80 | }
81 |
82 | fn name(&self) -> String {
83 | format!("{}", IssueDetectorNamePool::DeprecatedOzFunction)
84 | }
85 | }
86 |
87 | #[cfg(test)]
88 | mod deprecated_oz_functions_tests {
89 |
90 | use crate::detect::detector::IssueDetector;
91 |
92 | use super::DeprecatedOZFunctionDetector;
93 |
94 | #[test]
95 |
96 | fn test_deprecated_oz_functions_detector_by_loading_contract_directly() {
97 | let context = crate::detect::test_utils::load_solidity_source_unit(
98 | "../tests/contract-playground/src/DeprecatedOZFunctions.sol",
99 | );
100 |
101 | let mut detector = DeprecatedOZFunctionDetector::default();
102 | let found = detector.detect(&context).unwrap();
103 | assert!(found);
104 | assert_eq!(detector.instances().len(), 2);
105 | }
106 | }
107 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/mcp/contract_surface/tool.rs:
--------------------------------------------------------------------------------
```rust
1 | use super::render::*;
2 | use crate::{
3 | ast::{ASTNode, NodeID},
4 | context::{
5 | macros::{mcp_error, mcp_success},
6 | mcp::{
7 | MCPToolNamePool, ModelContextProtocolState, ModelContextProtocolTool,
8 | contract_surface::util::{
9 | get_classified_entrypoint_functions, get_inheritance_chain_info,
10 | get_total_state_variables,
11 | },
12 | },
13 | },
14 | };
15 | use indoc::indoc;
16 | use rmcp::{
17 | ErrorData as McpError, handler::server::wrapper::Parameters, model::CallToolResult, schemars,
18 | };
19 | use serde::Deserialize;
20 | use std::sync::Arc;
21 |
22 | #[derive(Clone)]
23 | pub struct ContractSurfaceTool {
24 | state: Arc<ModelContextProtocolState>,
25 | }
26 |
27 | #[derive(Deserialize, schemars::JsonSchema)]
28 | pub struct ContractSurfacePayload {
29 | /// The index of the compilation unit to analyze. Must be a positive integer starting from 1.
30 | /// Use the project overview tool first to see all available compilation units and their
31 | /// indices.
32 | pub compilation_unit_index: usize,
33 | /// The Node ID of the specific contract to analyze. Obtain this from the list contracts tool,
34 | /// which returns Node IDs for all deployable contracts in the compilation unit. Each contract
35 | /// has a unique Node ID within its compilation unit.
36 | pub node_id: NodeID,
37 | }
38 |
39 | impl ModelContextProtocolTool for ContractSurfaceTool {
40 | type Input = ContractSurfacePayload;
41 |
42 | fn new(state: Arc<ModelContextProtocolState>) -> Self {
43 | Self { state }
44 | }
45 |
46 | fn name(&self) -> String {
47 | MCPToolNamePool::AderynContractSurfaceInspector.to_string()
48 | }
49 |
50 | fn description(&self) -> String {
51 | indoc! {
52 | "Analyzes the surface area of a specific deployable contract within a compilation unit. Returns details\
53 | such as contract's state variables (own and inherited), and all entrypoint functions (own and inherited).\
54 | Use the Node ID from the list contracts tool to specify which contract to analyze."
55 | }
56 | .to_string()
57 | }
58 |
59 | fn execute(&self, input: Parameters<Self::Input>) -> Result<CallToolResult, McpError> {
60 | let payload = input.0;
61 |
62 | if payload.compilation_unit_index < 1
63 | || payload.compilation_unit_index > self.state.contexts.len()
64 | {
65 | return mcp_error!(
66 | "Invalid compilation unit index: {}. Must be in range [1, {}]",
67 | payload.compilation_unit_index,
68 | self.state.contexts.len()
69 | );
70 | }
71 |
72 | let context = self
73 | .state
74 | .contexts
75 | .get(payload.compilation_unit_index - 1)
76 | .expect("Compilation unit index bounds check failed");
77 |
78 | let Some(ASTNode::ContractDefinition(contract)) = context.nodes.get(&payload.node_id)
79 | else {
80 | return mcp_error!(
81 | "Node ID {} does not correspond to a contract definition",
82 | payload.node_id
83 | );
84 | };
85 |
86 | let (filepath, _, _) = context.get_node_sort_key_from_capturable(&contract.into());
87 | let total_state_variables = get_total_state_variables(context, contract);
88 | let reversed_chain = get_inheritance_chain_info(context, contract)?;
89 | let entrypoints = get_classified_entrypoint_functions(context, contract)?;
90 |
91 | let contract_surface = ContractSurfaceBuilder::default()
92 | .name(contract.name.clone())
93 | .node_id(payload.node_id)
94 | .filepath(filepath)
95 | .compilation_unit_index(payload.compilation_unit_index)
96 | .total_state_variables(total_state_variables)
97 | .reversed_chain(reversed_chain)
98 | .entrypoints(entrypoints)
99 | .build()
100 | .expect("failed to build contract surface");
101 |
102 | mcp_success!(contract_surface)
103 | }
104 | }
105 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/abi_encode_packed_hash_collision.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::NodeID,
5 | capture,
6 | context::workspace::WorkspaceContext,
7 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
8 | };
9 | use eyre::Result;
10 |
11 | #[derive(Default)]
12 | pub struct AvoidAbiEncodePackedDetector {
13 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
14 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
15 | found_instances: BTreeMap<(String, usize, String), NodeID>,
16 | }
17 |
18 | impl IssueDetector for AvoidAbiEncodePackedDetector {
19 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
20 | for member_access in context.member_accesses() {
21 | // If the member_access's member_name = "encodePacked", loop through the argument_types
22 | // and count how many of them contain any of the following in type_strings:
23 | // ["bytes ", "[]", "string"]
24 | // If the count is greater than 1, add the member_access to the found_abi_encode_packed
25 | // vector
26 | if member_access.member_name == "encodePacked" {
27 | let mut count = 0;
28 | let argument_types = member_access.argument_types.as_ref().unwrap();
29 | for argument_type in argument_types {
30 | if argument_type.type_string.as_ref().unwrap().contains("bytes ")
31 | || argument_type.type_string.as_ref().unwrap().contains("[]")
32 | || argument_type.type_string.as_ref().unwrap().contains("string")
33 | {
34 | count += 1;
35 | }
36 | }
37 | if count > 1 {
38 | capture!(self, context, member_access);
39 | }
40 | }
41 | }
42 | Ok(!self.found_instances.is_empty())
43 | }
44 |
45 | fn title(&self) -> String {
46 | String::from("`abi.encodePacked()` Hash Collision")
47 | }
48 |
49 | fn description(&self) -> String {
50 | String::from(
51 | "abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as `keccak256()`. \
52 | Use `abi.encode()` instead which will pad items to 32 bytes, preventing hash collisions: https://docs.soliditylang.org/en/v0.8.13/abi-spec.html#non-standard-packed-mode. \
53 | (e.g. `abi.encodePacked(0x123,0x456)` => `0x123456` => `abi.encodePacked(0x1,0x23456)`, but `abi.encode(0x123,0x456)` => `0x0...1230...456`). \
54 | Unless there is a compelling reason, `abi.encode` should be preferred. If there is only one argument to `abi.encodePacked()` \
55 | it can often be cast to `bytes()` or `bytes32()` instead: https://ethereum.stackexchange.com/questions/30912/how-to-compare-strings-in-solidity#answer-82739. \
56 | If all arguments are strings and or bytes, `bytes.concat()` should be used instead.",
57 | )
58 | }
59 |
60 | fn severity(&self) -> IssueSeverity {
61 | IssueSeverity::High
62 | }
63 |
64 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
65 | self.found_instances.clone()
66 | }
67 |
68 | fn name(&self) -> String {
69 | format!("{}", IssueDetectorNamePool::AbiEncodePackedHashCollision)
70 | }
71 | }
72 |
73 | #[cfg(test)]
74 | mod avoid_abi_encode_packed_tests {
75 |
76 | use crate::detect::detector::IssueDetector;
77 |
78 | use super::AvoidAbiEncodePackedDetector;
79 |
80 | #[test]
81 | fn test_avoid_abi_encode_packed_detectorby_by_loading_contract_directly() {
82 | let context = crate::detect::test_utils::load_solidity_source_unit(
83 | "../tests/contract-playground/src/KeccakContract.sol",
84 | );
85 |
86 | let mut detector = AvoidAbiEncodePackedDetector::default();
87 | let found = detector.detect(&context).unwrap();
88 | assert!(found);
89 | assert_eq!(detector.instances().len(), 3);
90 | }
91 | }
92 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/out_of_order_retryable.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{Expression, MemberAccess, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::{
8 | browser::ExtractFunctionCalls,
9 | graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
10 | workspace::WorkspaceContext,
11 | },
12 | detect::{
13 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
14 | helpers,
15 | },
16 | };
17 | use eyre::Result;
18 |
19 | #[derive(Default)]
20 | pub struct OutOfOrderRetryableDetector {
21 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
22 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
23 | found_instances: BTreeMap<(String, usize, String), NodeID>,
24 | }
25 |
26 | impl IssueDetector for OutOfOrderRetryableDetector {
27 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
28 | for func in helpers::get_implemented_external_and_public_functions(context) {
29 | let callgraphs =
30 | CallGraphConsumer::get(context, &[&(func.into())], CallGraphDirection::Inward)?;
31 | for callgraph in callgraphs {
32 | let mut tracker = OutOfOrderRetryableTracker { number_of_retry_calls: 0 };
33 | callgraph.accept(context, &mut tracker)?;
34 | if tracker.number_of_retry_calls >= 2 {
35 | capture!(self, context, func);
36 | }
37 | }
38 | }
39 |
40 | Ok(!self.found_instances.is_empty())
41 | }
42 |
43 | fn severity(&self) -> IssueSeverity {
44 | IssueSeverity::High
45 | }
46 |
47 | fn title(&self) -> String {
48 | String::from("Out of Order Retryable Transaction")
49 | }
50 |
51 | fn description(&self) -> String {
52 | String::from("Do not rely on the order or successful execution of retryable tickets. Functions like \
53 | createRetryableTicket, outboundTransferCustomRefund, unsafeCreateRetryableTicket are free to be re-tried in any
54 | order if they fail in the first go. Since this operation happens off chain, the sequencer is in control of the
55 | order of these transactions. Therefore, restrict the use to at most 1 ticket call per function.")
56 | }
57 |
58 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
59 | self.found_instances.clone()
60 | }
61 |
62 | fn name(&self) -> String {
63 | format!("{}", IssueDetectorNamePool::OutOfOrderRetryable)
64 | }
65 | }
66 |
67 | struct OutOfOrderRetryableTracker {
68 | number_of_retry_calls: usize,
69 | }
70 |
71 | const SEQUENCER_FUNCTIONS: [&str; 3] =
72 | ["createRetryableTicket", "outboundTransferCustomRefund", "unsafeCreateRetryableTicket"];
73 |
74 | impl CallGraphVisitor for OutOfOrderRetryableTracker {
75 | fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
76 | if self.number_of_retry_calls >= 2 {
77 | return Ok(());
78 | }
79 | let function_calls = ExtractFunctionCalls::from(node).extracted;
80 | for func_call in function_calls {
81 | if let Expression::MemberAccess(MemberAccess { member_name, .. }) =
82 | func_call.expression.as_ref()
83 | && SEQUENCER_FUNCTIONS.iter().any(|f| f == member_name)
84 | {
85 | self.number_of_retry_calls += 1;
86 | }
87 | }
88 | Ok(())
89 | }
90 | }
91 |
92 | #[cfg(test)]
93 | mod out_of_order_retryable_tests {
94 |
95 | use crate::detect::{
96 | detector::IssueDetector, high::out_of_order_retryable::OutOfOrderRetryableDetector,
97 | };
98 |
99 | #[test]
100 |
101 | fn test_out_of_order_retryable() {
102 | let context = crate::detect::test_utils::load_solidity_source_unit(
103 | "../tests/contract-playground/src/OutOfOrderRetryable.sol",
104 | );
105 |
106 | let mut detector = OutOfOrderRetryableDetector::default();
107 | let found = detector.detect(&context).unwrap();
108 | assert!(found);
109 | assert_eq!(detector.instances().len(), 2);
110 | }
111 | }
112 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/unchecked_low_level_call.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{ASTNode, NodeID, NodeType};
4 |
5 | use crate::{
6 | capture,
7 | context::{
8 | browser::{GetClosestAncestorOfTypeX, GetImmediateParent},
9 | workspace::WorkspaceContext,
10 | },
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct UncheckedLowLevelCallDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for UncheckedLowLevelCallDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | let call_types = ["call", "staticcall", "delegatecall"];
25 | for member_access in context.member_accesses() {
26 | if call_types.iter().any(|&c| c == member_access.member_name)
27 | && member_access.expression.type_descriptions().is_some_and(|type_desc| {
28 | type_desc.type_string.as_ref().is_some_and(|type_string| {
29 | type_string == "address" || type_string == "address payable"
30 | })
31 | })
32 | && let Some(ASTNode::FunctionCall(func_call)) =
33 | member_access.closest_ancestor_of_type(context, NodeType::FunctionCall)
34 | {
35 | // In most cases, it's enough to check if the function call's parent is Block
36 | // But to cover this case - dst.call.value(msg.value)("");
37 | // We need to also check for the possibility where the function call's parent is
38 | // another function call and that has a direct parent of type block
39 | if let Some(ASTNode::ExpressionStatement(e)) = func_call.parent(context)
40 | && e.parent(context).is_some_and(|node| node.node_type() == NodeType::Block)
41 | {
42 | capture!(self, context, func_call);
43 | }
44 |
45 | if let Some(ASTNode::FunctionCall(outside_parent)) = func_call.parent(context)
46 | && let Some(ASTNode::ExpressionStatement(e)) = outside_parent.parent(context)
47 | && e.parent(context).is_some_and(|node| node.node_type() == NodeType::Block)
48 | {
49 | capture!(self, context, func_call);
50 | }
51 | }
52 | }
53 |
54 | Ok(!self.found_instances.is_empty())
55 | }
56 |
57 | fn severity(&self) -> IssueSeverity {
58 | IssueSeverity::High
59 | }
60 |
61 | fn title(&self) -> String {
62 | String::from("Unchecked Low level calls")
63 | }
64 |
65 | fn description(&self) -> String {
66 | String::from(
67 | "The return value of the low-level call is not checked, so if the call fails, the Ether will be locked in the contract. If the low level is used to prevent blocking operations, consider logging failed calls. Ensure that the return value of a low-level call is checked or logged.",
68 | )
69 | }
70 |
71 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
72 | self.found_instances.clone()
73 | }
74 |
75 | fn name(&self) -> String {
76 | format!("{}", IssueDetectorNamePool::UncheckedLowLevelCall)
77 | }
78 | }
79 |
80 | #[cfg(test)]
81 | mod unchecked_low_level_calls_tests {
82 |
83 | use crate::detect::{
84 | detector::IssueDetector, high::unchecked_low_level_call::UncheckedLowLevelCallDetector,
85 | };
86 |
87 | #[test]
88 |
89 | fn test_unchecked_low_level_calls() {
90 | let context = crate::detect::test_utils::load_solidity_source_unit(
91 | "../tests/contract-playground/src/UncheckedCalls.sol",
92 | );
93 |
94 | let mut detector = UncheckedLowLevelCallDetector::default();
95 | let found = detector.detect(&context).unwrap();
96 |
97 | assert!(found);
98 | assert_eq!(detector.instances().len(), 9);
99 | }
100 | }
101 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unused_public_function.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::{FunctionKind, NodeID, Visibility},
5 | capture,
6 | context::{
7 | browser::GetClosestAncestorOfTypeX,
8 | workspace::{ASTNode, WorkspaceContext},
9 | },
10 | detect::{
11 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | helpers::count_identifiers_that_reference_an_id,
13 | },
14 | };
15 | use eyre::Result;
16 |
17 | #[derive(Default)]
18 | pub struct UnusedPublicFunctionDetector {
19 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
20 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
21 | found_instances: BTreeMap<(String, usize, String), NodeID>,
22 | }
23 |
24 | impl IssueDetector for UnusedPublicFunctionDetector {
25 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
26 | let unreferenced_public_functions =
27 | context.function_definitions().into_iter().filter(|&function| {
28 | matches!(function.visibility, Visibility::Public)
29 | && !matches!(function.kind(), &FunctionKind::Constructor)
30 | && function.overrides.is_none()
31 | && !function.is_virtual
32 | && count_identifiers_that_reference_an_id(context, function.id) == 0
33 | });
34 |
35 | for unreferenced_public_function in unreferenced_public_functions {
36 | if let Some(ASTNode::ContractDefinition(parent_contract)) = unreferenced_public_function
37 | .closest_ancestor_of_type(context, crate::ast::NodeType::ContractDefinition)
38 | && parent_contract.is_abstract
39 | {
40 | continue;
41 | }
42 | capture!(self, context, unreferenced_public_function);
43 | }
44 |
45 | Ok(!self.found_instances.is_empty())
46 | }
47 |
48 | fn title(&self) -> String {
49 | String::from("Public Function Not Used Internally")
50 | }
51 |
52 | fn description(&self) -> String {
53 | String::from(
54 | "If a function is marked public but is not used internally, consider marking it as `external`.",
55 | )
56 | }
57 |
58 | fn severity(&self) -> IssueSeverity {
59 | IssueSeverity::Low
60 | }
61 |
62 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
63 | self.found_instances.clone()
64 | }
65 |
66 | fn name(&self) -> String {
67 | format!("{}", IssueDetectorNamePool::UnusedPublicFunction)
68 | }
69 | }
70 |
71 | #[cfg(test)]
72 | mod useless_public_function_tests {
73 | use crate::detect::detector::IssueDetector;
74 |
75 | use super::UnusedPublicFunctionDetector;
76 |
77 | #[test]
78 |
79 | fn test_useless_public_functions_by_loading_contract_directly() {
80 | let context = crate::detect::test_utils::load_solidity_source_unit(
81 | "../tests/contract-playground/src/Counter.sol",
82 | );
83 |
84 | let mut detector = UnusedPublicFunctionDetector::default();
85 | let found = detector.detect(&context).unwrap();
86 | assert!(found);
87 | assert_eq!(detector.instances().len(), 1);
88 | }
89 |
90 | #[test]
91 |
92 | fn test_useless_public_functions_does_not_capture_abstract_contract_functions() {
93 | let context = crate::detect::test_utils::load_solidity_source_unit(
94 | "../tests/contract-playground/src/AbstractContract.sol",
95 | );
96 |
97 | let mut detector = UnusedPublicFunctionDetector::default();
98 | let found = detector.detect(&context).unwrap();
99 | assert!(!found);
100 | assert_eq!(detector.instances().len(), 0);
101 | }
102 |
103 | #[test]
104 |
105 | fn test_useless_public_functions_does_not_capture_virtual_or_overriding_functions() {
106 | let context = crate::detect::test_utils::load_solidity_source_unit(
107 | "../tests/contract-playground/src/PublicFunction.sol",
108 | );
109 |
110 | let mut detector = UnusedPublicFunctionDetector::default();
111 | let found = detector.detect(&context).unwrap();
112 | assert!(!found);
113 | assert_eq!(detector.instances().len(), 0);
114 | }
115 | }
116 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high.rs:
--------------------------------------------------------------------------------
```rust
1 | pub(crate) mod abi_encode_packed_hash_collision;
2 | pub(crate) mod arbitrary_transfer_from;
3 | pub(crate) mod const_func_changes_state;
4 | pub(crate) mod contract_locks_ether;
5 | pub(crate) mod dangerous_unary_operator;
6 | pub(crate) mod delegate_call_unchecked_address;
7 | pub(crate) mod delete_nested_mapping;
8 | pub(crate) mod dynamic_array_length_assignment;
9 | pub(crate) mod enumerable_loop_removal;
10 | pub(crate) mod eth_send_unchecked_address;
11 | pub(crate) mod experimental_encoder;
12 | pub(crate) mod function_selector_collision;
13 | pub(crate) mod incorrect_caret_operator;
14 | pub(crate) mod incorrect_erc20_interface;
15 | pub(crate) mod incorrect_erc721_interface;
16 | pub(crate) mod incorrect_shift_order;
17 | pub(crate) mod misused_boolean;
18 | pub(crate) mod msg_value_in_loops;
19 | pub(crate) mod multiple_constructors;
20 | pub(crate) mod nested_struct_in_mapping;
21 | pub(crate) mod out_of_order_retryable;
22 | pub(crate) mod pre_declared_variable_usage;
23 | pub(crate) mod reentrancy_state_change;
24 | pub(crate) mod reused_contract_name;
25 | pub(crate) mod rtlo;
26 | pub(crate) mod selfdestruct;
27 | pub(crate) mod signed_integer_storage_array;
28 | pub(crate) mod state_variable_shadowing;
29 | pub(crate) mod storage_array_memory_edit;
30 | pub(crate) mod strict_equality_contract_balance;
31 | pub(crate) mod tautological_compare;
32 | pub(crate) mod tautology_or_contradiction;
33 | pub(crate) mod tx_origin_used_for_auth;
34 | pub(crate) mod unchecked_low_level_call;
35 | pub(crate) mod unchecked_send;
36 | pub(crate) mod unprotected_initializer;
37 | pub(crate) mod unsafe_casting;
38 | pub(crate) mod weak_randomness;
39 | pub(crate) mod yul_return;
40 |
41 | pub use abi_encode_packed_hash_collision::AvoidAbiEncodePackedDetector;
42 | pub use arbitrary_transfer_from::ArbitraryTransferFromDetector;
43 | pub use const_func_changes_state::ConstantFunctionChangesStateDetector;
44 | pub use contract_locks_ether::ContractLocksEtherDetector;
45 | pub use dangerous_unary_operator::DangerousUnaryOperatorDetector;
46 | pub use delegate_call_unchecked_address::DelegateCallUncheckedAddressDetector;
47 | pub use delete_nested_mapping::DeletionNestedMappingDetector;
48 | pub use dynamic_array_length_assignment::DynamicArrayLengthAssignmentDetector;
49 | pub use enumerable_loop_removal::EnumerableLoopRemovalDetector;
50 | pub use eth_send_unchecked_address::SendEtherNoChecksDetector;
51 | pub use experimental_encoder::ExperimentalEncoderDetector;
52 | pub use function_selector_collision::FunctionSelectorCollisionDetector;
53 | pub use incorrect_caret_operator::IncorrectUseOfCaretOperatorDetector;
54 | pub use incorrect_erc20_interface::IncorrectERC20InterfaceDetector;
55 | pub use incorrect_erc721_interface::IncorrectERC721InterfaceDetector;
56 | pub use incorrect_shift_order::IncorrectShiftOrderDetector;
57 | pub use misused_boolean::MisusedBooleanDetector;
58 | pub use msg_value_in_loops::MsgValueUsedInLoopDetector;
59 | pub use multiple_constructors::MultipleConstructorsDetector;
60 | pub use nested_struct_in_mapping::NestedStructInMappingDetector;
61 | pub use out_of_order_retryable::OutOfOrderRetryableDetector;
62 | pub use pre_declared_variable_usage::PreDeclaredLocalVariableUsageDetector;
63 | pub use reentrancy_state_change::ReentrancyStateChangeDetector;
64 | pub use reused_contract_name::ReusedContractNameDetector;
65 | pub use rtlo::RTLODetector;
66 | pub use selfdestruct::SelfdestructDetector;
67 | pub use signed_integer_storage_array::StorageSignedIntegerArrayDetector;
68 | pub use state_variable_shadowing::StateVariableShadowingDetector;
69 | pub use storage_array_memory_edit::StorageArrayMemoryEditDetector;
70 | pub use strict_equality_contract_balance::DangerousStrictEqualityOnBalanceDetector;
71 | pub use tautological_compare::TautologicalCompareDetector;
72 | pub use tautology_or_contradiction::TautologyOrContraditionDetector;
73 | pub use tx_origin_used_for_auth::TxOriginUsedForAuthDetector;
74 | pub use unchecked_low_level_call::UncheckedLowLevelCallDetector;
75 | pub use unchecked_send::UncheckedSendDetector;
76 | pub use unprotected_initializer::UnprotectedInitializerDetector;
77 | pub use unsafe_casting::UnsafeCastingDetector;
78 | pub use weak_randomness::WeakRandomnessDetector;
79 | pub use yul_return::YulReturnDetector;
80 |
```
--------------------------------------------------------------------------------
/aderyn_driver/src/process.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{
2 | compile,
3 | config::supplement_values_from_aderyn_toml,
4 | driver::{CliArgsCommonConfig, CliArgsInputConfig},
5 | };
6 | use aderyn_core::{
7 | context::{
8 | graph::{LegacyWorkspaceCallGraph, Transpose, WorkspaceCallGraphs},
9 | router::Router,
10 | workspace::WorkspaceContext,
11 | },
12 | stats,
13 | };
14 | use solidity_ast::ProjectConfigInput;
15 | use std::{
16 | collections::HashMap,
17 | path::{Path, PathBuf},
18 | };
19 |
20 | pub struct WorkspaceContextWrapper {
21 | pub contexts: Vec<WorkspaceContext>,
22 | pub root_path: PathBuf,
23 | pub project_config: ProjectConfigInput,
24 | }
25 |
26 | pub struct PreprocessedConfig {
27 | pub root_path: PathBuf,
28 | pub src: Option<String>,
29 | pub include: Option<Vec<String>>,
30 | pub exclude: Option<Vec<String>>,
31 | }
32 |
33 | pub fn make_context(
34 | args: &CliArgsInputConfig,
35 | common: &CliArgsCommonConfig,
36 | ) -> Result<WorkspaceContextWrapper, Box<dyn std::error::Error + Send + Sync>> {
37 | // Preprocess config by supplementing CLI args with aderyn.toml if exists
38 | let preprocessed_config = obtain_config_values(args.clone())?;
39 |
40 | let root_path = preprocessed_config.root_path.clone();
41 |
42 | // Compilation steps:
43 | // 1. Processes the above preprocessed config by translating them to runtime values Thanks to
44 | // Cyfrin/solidity-ast-rs
45 | // 2. Parse those files and prepare ASTs.
46 | let (mut contexts, project_config) =
47 | compile::compile_project(preprocessed_config, common.lsp, common.verbose)?;
48 |
49 | // Only make this an error when it's not in LSP mode
50 | if !common.lsp && contexts.iter().all(|c| c.src_filepaths.is_empty()) {
51 | let error = "No solidity files found in given scope!";
52 | eprintln!("{}", error);
53 | return Err(error.into());
54 | }
55 |
56 | // Supplement the context
57 | // 1. Inject nSLOC stats
58 | // 2. Collect lines marked by aderyn-ignore-line, aderyn-ignore-next-line
59 | // 3. Inject Legacy Callgraph
60 | // 4. Inject Router
61 | // 5. Inject New Callgraph
62 | for context in contexts.iter_mut() {
63 | let absolute_root_path = &ensure_valid_root_path(&root_path);
64 | let stats = stats::collect_stats(absolute_root_path.as_path(), common.skip_cloc, context);
65 | let sloc_stats: HashMap<String, usize> =
66 | stats.iter().map(|(key, value)| (key.to_owned(), value.code)).collect();
67 |
68 | let ignore_line_stats: HashMap<String, Vec<stats::IgnoreLine>> =
69 | stats.iter().map(|(key, value)| (key.to_owned(), value.ignore_lines.clone())).collect();
70 |
71 | context.set_sloc_stats(sloc_stats);
72 | context.set_ignore_lines_stats(ignore_line_stats);
73 |
74 | let inward_callgraph = LegacyWorkspaceCallGraph::from_context(context)?;
75 | let outward_callgraph =
76 | LegacyWorkspaceCallGraph { raw_callgraph: inward_callgraph.raw_callgraph.reverse() };
77 | context.inward_callgraph = Some(inward_callgraph);
78 | context.outward_callgraph = Some(outward_callgraph);
79 |
80 | let router = Router::build(context);
81 | context.router = Some(router);
82 |
83 | let callgraphs = WorkspaceCallGraphs::build(context);
84 | context.callgraphs = Some(callgraphs);
85 | }
86 |
87 | Ok(WorkspaceContextWrapper { contexts, root_path, project_config })
88 | }
89 |
90 | /// Supplement the CLI arguments with values from aderyn.toml
91 | fn obtain_config_values(
92 | args: CliArgsInputConfig,
93 | ) -> Result<PreprocessedConfig, Box<dyn std::error::Error + Send + Sync>> {
94 | let root_path = PathBuf::from(&args.root);
95 | let aderyn_path = root_path.join("aderyn.toml");
96 |
97 | let current = PreprocessedConfig {
98 | root_path,
99 | src: args.src,
100 | exclude: args.path_excludes,
101 | include: args.path_includes,
102 | };
103 |
104 | // Process aderyn.toml if it exists
105 | if aderyn_path.exists() {
106 | return supplement_values_from_aderyn_toml(current);
107 | }
108 | Ok(current)
109 | }
110 |
111 | fn ensure_valid_root_path(root_path: &Path) -> PathBuf {
112 | if !root_path.exists() {
113 | eprintln!("{} does not exist!", root_path.to_string_lossy());
114 | std::process::exit(1);
115 | }
116 | std::fs::canonicalize(root_path).unwrap()
117 | }
118 |
```
--------------------------------------------------------------------------------
/aderyn_core/templates/mcp-tool-response/contract_surface.md:
--------------------------------------------------------------------------------
```markdown
1 | ## Contract Surface Inspector for {{ name }}
2 |
3 | *Contract name:* {{ name }}
4 |
5 | *Contract ID:* {{ node_id }}
6 |
7 | *Compilation Unit index:* {{ compilation_unit_index }}
8 |
9 | *Filepath:* {{ filepath }}
10 |
11 | ### C3 Linearized Inheritance
12 |
13 | After performing C3 linearization on the main contract {{ name }}, the following chain is the end result. It goes from the most base parent class at the beginning to the most derived class at the end of the list chain.
14 |
15 | The last contract class in the chain is {{ name }} because it is the most derived contract class in its hierarchy.
16 |
17 | List of names of contract class in the inheritance chain of {{ name }} along with their filepath.
18 |
19 | **Total contracts in inheritance chain:** {{ reversed_chain.len() }}
20 |
21 | {% for c in reversed_chain %}
22 | {{ loop.index }}. {{ c.name }} | Node ID: {{ c.node_id }} | Filepath: {{ c.filepath }}
23 | {% endfor %}
24 |
25 | ### State variables
26 |
27 | State variables of a contract include all variables defined in the contract itself plus those inherited from parent classes.
28 |
29 | {% if total_state_variables == 0 %}
30 | *No state variables found*
31 | {% else %}
32 | The following code snippets show all state variables defined in each contract class within the inheritance chain.
33 |
34 | {% for c in reversed_chain %}
35 | {{ loop.index }}. {{ c.name }}
36 |
37 | {% if c.state_variables.len() == 0 %}
38 | No state variables found defined in {{ c.name }}
39 | {% else %}
40 | ```solidity
41 | {% for s in c.state_variables %}
42 | {{ s }}
43 | {% endfor %}
44 | ```
45 | {% endif %}
46 |
47 | {% endfor %}
48 |
49 | {% endif %}
50 |
51 | ### Entrypoint functions
52 |
53 | In Solidity, entrypoint functions are functions that can be called from outside the contract or serve as initial execution points. The main types of entrypoint functions are external functions, public functions, fallback functions, and receive functions.
54 |
55 | The following is a list of entrypoint functions for {{ name }} with their corresponding Node IDs and the contract class in the inheritance chain where each function is defined.
56 |
57 | If a function has been overridden, it won't be listed in here. This list contains all the functions after filtering out the ones that are actually called.
58 |
59 | #### External Functions
60 |
61 | {% if entrypoints.external_functions.len() == 0 %}
62 | *No external functions found*
63 | {% else %}
64 | {% for func in entrypoints.external_functions %}
65 | {{ loop.index }}. **{{ func.name }}** | Function's Node Id: {{ func.node_id }} | Containing contract class: {{ func.containing_contract.name }} | Containing contract's Node Id: {{ func.containing_contract.node_id }}
66 | {% endfor %}
67 | {% endif %}
68 |
69 | #### Public Functions
70 |
71 | {% if entrypoints.public_functions.len() == 0 %}
72 | *No public functions found*
73 | {% else %}
74 | {% for func in entrypoints.public_functions %}
75 | {{ loop.index }}. **{{ func.name }}** | Function's Node Id: {{ func.node_id }} | Containing contract class: {{ func.containing_contract.name }} | Containing contract's Node Id: {{ func.containing_contract.node_id }}
76 | {% endfor %}
77 | {% endif %}
78 |
79 | #### Fallback Function
80 |
81 | {% if let Some(fallback_function) = entrypoints.fallback_function %}
82 | Node Id: {{ fallback_function.node_id }} | Containing contract class: {{ fallback_function.containing_contract.name }} | Containing contract's Node Id: {{ fallback_function.containing_contract.node_id }}
83 | {% else %}
84 | *No fallback function found.*
85 | {% endif %}
86 |
87 | #### Receive Function
88 |
89 | {% if let Some(receive_function) = entrypoints.receive_function %}
90 | Node Id: {{ receive_function.node_id }} | Containing contract class: {{ receive_function.containing_contract.name }} | Containing contract's Node Id: {{ receive_function.containing_contract.node_id }}
91 | {% else %}
92 | *No receive function found.*
93 | {% endif %}
94 |
95 | ### Suggestion for next steps:
96 |
97 | Try to explore callgraphs starting from a given entrypoint function. To do that, use the callgraph provider tool and pass the entrypoint function's Node ID, and the Node ID of the original deployable contract (same one used to call this contract surface tool). NOT just the containing contract's Node ID.
98 |
99 | A good analysis explores the project on a per callgraph basis for entrypoints of interest. This provides a holistic picture and therefore less false positives.
100 |
```
--------------------------------------------------------------------------------
/benchmarks/report/index.html:
--------------------------------------------------------------------------------
```html
1 | <!DOCTYPE html>
2 | <html>
3 |
4 | <head>
5 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6 | <title>Index - Criterion.rs</title>
7 | <style type="text/css">
8 | body {
9 | font: 14px Helvetica Neue;
10 | text-rendering: optimizelegibility;
11 | }
12 |
13 | .body {
14 | width: 960px;
15 | margin: auto;
16 | }
17 |
18 | a:link {
19 | color: #1F78B4;
20 | text-decoration: none;
21 | }
22 |
23 | h2 {
24 | font-size: 36px;
25 | font-weight: 300;
26 | }
27 |
28 | h3 {
29 | font-size: 24px;
30 | font-weight: 300;
31 | }
32 |
33 | #footer {
34 | height: 40px;
35 | background: #888;
36 | color: white;
37 | font-size: larger;
38 | font-weight: 300;
39 | }
40 |
41 | #footer a {
42 | color: white;
43 | text-decoration: underline;
44 | }
45 |
46 | #footer p {
47 | text-align: center
48 | }
49 |
50 | table {
51 | border-collapse: collapse;
52 | }
53 |
54 | table,
55 | th,
56 | td {
57 | border: 1px solid #888;
58 | }
59 | </style>
60 | </head>
61 |
62 | <body>
63 | <div class="body">
64 | <h2>Criterion.rs Benchmark Index</h2>
65 | See individual benchmark pages below for more details.
66 | <ul>
67 | <li><a href="../aderyn/report/index.html">aderyn</a></li>
68 | <li><a href="../arbitrary-transfer-from/report/index.html">arbitrary-transfer-from</a></li>
69 | <li><a href="../avoid-abi-encode-packed/report/index.html">avoid-abi-encode-packed</a></li>
70 | <li><a href="../block-timestamp-deadline/report/index.html">block-timestamp-deadline</a></li>
71 | <li><a href="../centralization-risk/report/index.html">centralization-risk</a></li>
72 | <li><a href="../constants-instead-of-literals/report/index.html">constants-instead-of-literals</a></li>
73 | <li><a href="../delegate-call-in-loop/report/index.html">delegate-call-in-loop</a></li>
74 | <li><a href="../deprecated-oz-functions/report/index.html">deprecated-oz-functions</a></li>
75 | <li><a href="../ecrecover/report/index.html">ecrecover</a></li>
76 | <li><a href="../empty-block/report/index.html">empty-block</a></li>
77 | <li><a href="../hello_world/report/index.html">hello_world</a></li>
78 | <li><a href="../inconsistent-type-names/report/index.html">inconsistent-type-names</a></li>
79 | <li><a href="../large-numeric-literal/report/index.html">large-numeric-literal</a></li>
80 | <li><a href="../non-reentrant-before-others/report/index.html">non-reentrant-before-others</a></li>
81 | <li><a href="../push-zero-opcode/report/index.html">push-zero-opcode</a></li>
82 | <li><a href="../require-with-string/report/index.html">require-with-string</a></li>
83 | <li><a href="../solmate-safe-transfer-lib/report/index.html">solmate-safe-transfer-lib</a></li>
84 | <li><a href="../unindexed-events/report/index.html">unindexed-events</a></li>
85 | <li><a href="../unprotected-initializer/report/index.html">unprotected-initializer</a></li>
86 | <li><a href="../unsafe-erc20-functions/report/index.html">unsafe-erc20-functions</a></li>
87 | <li><a href="../unsafe-oz-erc721-mint/report/index.html">unsafe-oz-erc721-mint</a></li>
88 | <li><a href="../unspecific-solidity-pragma/report/index.html">unspecific-solidity-pragma</a></li>
89 | <li><a href="../useless-internal-function/report/index.html">useless-internal-function</a></li>
90 | <li><a href="../useless-modifier/report/index.html">useless-modifier</a></li>
91 | <li><a href="../useless-public-function/report/index.html">useless-public-function</a></li>
92 | <li><a href="../zero-address-check/report/index.html">zero-address-check</a></li>
93 | </ul>
94 | </div>
95 | <div id="footer">
96 | <p>This report was generated by
97 | <a href="https://github.com/bheisler/criterion.rs">Criterion.rs</a>, a statistics-driven benchmarking
98 | library in Rust.</p>
99 | </div>
100 | </body>
101 | </html>
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/mcp/contract_surface/util.rs:
--------------------------------------------------------------------------------
```rust
1 | use super::render::*;
2 | use crate::{
3 | ast::{ASTNode, ContractDefinition, FunctionKind, NodeType, Visibility},
4 | context::{
5 | browser::{ExtractVariableDeclarations, GetClosestAncestorOfTypeX},
6 | workspace::WorkspaceContext,
7 | },
8 | };
9 | use rmcp::ErrorData as McpError;
10 |
11 | pub fn get_total_state_variables(
12 | context: &WorkspaceContext,
13 | original_contract: &ContractDefinition,
14 | ) -> usize {
15 | let Some(state_vars) =
16 | original_contract.get_all_state_variables_in_linearized_base_contracts_chain(context)
17 | else {
18 | return 0;
19 | };
20 | state_vars.len()
21 | }
22 |
23 | pub fn get_classified_entrypoint_functions(
24 | context: &WorkspaceContext,
25 | original_contract: &ContractDefinition,
26 | ) -> Result<EntrypointFunctions, McpError> {
27 | let Some(functions) = context.entrypoint_functions(original_contract) else {
28 | // TODO: investigate when you hit this case, it has been a while since I made the router
29 | return Ok(EntrypointFunctions::default());
30 | };
31 | let mut public_functions_info = vec![];
32 | let mut external_functions_info = vec![];
33 | let mut receive_function_info = None;
34 | let mut fallback_function_info = None;
35 | for func in functions {
36 | let Some(ASTNode::ContractDefinition(container)) =
37 | func.closest_ancestor_of_type(context, NodeType::ContractDefinition)
38 | else {
39 | continue;
40 | };
41 | let container = ContainingContractBuilder::default()
42 | .name(container.name.clone())
43 | .node_id(container.id)
44 | .build()
45 | .expect("failed to build container");
46 |
47 | let function_info = FunctionInfoBuilder::default()
48 | .name(func.name.clone())
49 | .node_id(func.id)
50 | .containing_contract(container)
51 | .build()
52 | .expect("failed to build function info");
53 |
54 | match *func.kind() {
55 | FunctionKind::Function => match func.visibility {
56 | Visibility::Public => {
57 | public_functions_info.push(function_info);
58 | }
59 | Visibility::External => {
60 | external_functions_info.push(function_info);
61 | }
62 | Visibility::Internal | Visibility::Private => {} // not an entrypoint
63 | },
64 | FunctionKind::Receive => {
65 | receive_function_info = Some(function_info);
66 | }
67 | FunctionKind::Fallback => {
68 | fallback_function_info = Some(function_info);
69 | }
70 | FunctionKind::Constructor => {} // For now, constructor is not an entrypoint
71 | FunctionKind::FreeFunction => unreachable!(), // Free function is never an entrypoint
72 | };
73 | }
74 |
75 | let entrypoints = EntrypointFunctionsBuilder::default()
76 | .external_functions(external_functions_info)
77 | .public_functions(public_functions_info)
78 | .fallback_function(fallback_function_info)
79 | .receive_function(receive_function_info)
80 | .build()
81 | .expect("failed to build entrypoints in inheritance chain");
82 | Ok(entrypoints)
83 | }
84 |
85 | pub fn get_inheritance_chain_info(
86 | context: &WorkspaceContext,
87 | original_contract: &ContractDefinition,
88 | ) -> Result<Vec<ContractInfo>, McpError> {
89 | let mut reversed_chain = vec![];
90 | for contract in original_contract.c3(context).collect::<Vec<_>>().into_iter().rev() {
91 | let variables = ExtractVariableDeclarations::from(contract).extracted;
92 | let state_variables = variables
93 | .iter()
94 | .filter(|v| v.state_variable)
95 | .map(|v| v.name.clone())
96 | .collect::<Vec<_>>();
97 | let (filepath, _, _) = context.get_node_sort_key_from_capturable(&contract.into());
98 |
99 | let contract_info = ContractInfoBuilder::default()
100 | .name(contract.name.clone())
101 | .node_id(contract.id)
102 | .state_variables(state_variables)
103 | .filepath(filepath)
104 | .build()
105 | .expect("failed to build contract info in inheritance chain");
106 | reversed_chain.push(contract_info);
107 | }
108 | Ok(reversed_chain)
109 | }
110 |
```
--------------------------------------------------------------------------------
/tests/ast/override.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"A":[5],"B":[16],"C":[29]},"id":30,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"A","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":5,"linearizedBaseContracts":[5],"name":"A","nameLocation":"9:1:1","nodeType":"ContractDefinition","nodes":[{"body":{"id":3,"nodeType":"Block","src":"44:2:1","statements":[]},"functionSelector":"a399b6a2","id":4,"implemented":true,"kind":"function","modifiers":[],"name":"faa","nameLocation":"23:3:1","nodeType":"FunctionDefinition","parameters":{"id":1,"nodeType":"ParameterList","parameters":[],"src":"26:2:1"},"returnParameters":{"id":2,"nodeType":"ParameterList","parameters":[],"src":"44:0:1"},"scope":5,"src":"14:32:1","stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"scope":30,"src":"0:48:1","usedErrors":[]},{"abstract":true,"baseContracts":[{"baseName":{"id":6,"name":"A","nameLocations":["72:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":5,"src":"72:1:1"},"id":7,"nodeType":"InheritanceSpecifier","src":"72:1:1"}],"canonicalName":"B","contractDependencies":[],"contractKind":"contract","fullyImplemented":false,"id":16,"linearizedBaseContracts":[16,5],"name":"B","nameLocation":"67:1:1","nodeType":"ContractDefinition","nodes":[{"functionSelector":"c2985578","id":10,"implemented":false,"kind":"function","modifiers":[],"name":"foo","nameLocation":"86:3:1","nodeType":"FunctionDefinition","parameters":{"id":8,"nodeType":"ParameterList","parameters":[],"src":"89:2:1"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[],"src":"106:0:1"},"scope":16,"src":"77:30:1","stateMutability":"nonpayable","virtual":true,"visibility":"public"},{"baseFunctions":[4],"body":{"id":14,"nodeType":"Block","src":"148:2:1","statements":[]},"functionSelector":"a399b6a2","id":15,"implemented":true,"kind":"function","modifiers":[],"name":"faa","nameLocation":"118:3:1","nodeType":"FunctionDefinition","overrides":{"id":12,"nodeType":"OverrideSpecifier","overrides":[],"src":"139:8:1"},"parameters":{"id":11,"nodeType":"ParameterList","parameters":[],"src":"121:2:1"},"returnParameters":{"id":13,"nodeType":"ParameterList","parameters":[],"src":"148:0:1"},"scope":16,"src":"109:41:1","stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"scope":30,"src":"49:103:1","usedErrors":[]},{"abstract":false,"baseContracts":[{"baseName":{"id":17,"name":"B","nameLocations":["167:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":16,"src":"167:1:1"},"id":18,"nodeType":"InheritanceSpecifier","src":"167:1:1"}],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":29,"linearizedBaseContracts":[29,16,5],"name":"C","nameLocation":"162:1:1","nodeType":"ContractDefinition","nodes":[{"baseFunctions":[10],"body":{"id":22,"nodeType":"Block","src":"203:3:1","statements":[]},"functionSelector":"c2985578","id":23,"implemented":true,"kind":"function","modifiers":[],"name":"foo","nameLocation":"181:3:1","nodeType":"FunctionDefinition","overrides":{"id":20,"nodeType":"OverrideSpecifier","overrides":[],"src":"194:8:1"},"parameters":{"id":19,"nodeType":"ParameterList","parameters":[],"src":"184:2:1"},"returnParameters":{"id":21,"nodeType":"ParameterList","parameters":[],"src":"203:0:1"},"scope":29,"src":"172:34:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"baseFunctions":[15],"body":{"id":27,"nodeType":"Block","src":"239:3:1","statements":[]},"functionSelector":"a399b6a2","id":28,"implemented":true,"kind":"function","modifiers":[],"name":"faa","nameLocation":"217:3:1","nodeType":"FunctionDefinition","overrides":{"id":25,"nodeType":"OverrideSpecifier","overrides":[],"src":"230:8:1"},"parameters":{"id":24,"nodeType":"ParameterList","parameters":[],"src":"220:2:1"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[],"src":"239:0:1"},"scope":29,"src":"208:34:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":30,"src":"153:91:1","usedErrors":[]}],"src":"0:245:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/costly_loop.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, convert::identity, error::Error};
2 |
3 | use crate::ast::{ASTNode, NodeID};
4 |
5 | use crate::{capture, context::browser::ApproximateStorageChangeFinder};
6 |
7 | use crate::{
8 | context::{
9 | graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
10 | workspace::WorkspaceContext,
11 | },
12 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
13 | };
14 | use eyre::Result;
15 |
16 | #[derive(Default)]
17 | pub struct CostlyLoopDetector {
18 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
19 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
20 | found_instances: BTreeMap<(String, usize, String), NodeID>,
21 | }
22 |
23 | impl IssueDetector for CostlyLoopDetector {
24 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
25 | // Investigate for loops to check for storage writes
26 | for for_statement in context.for_statements() {
27 | if changes_state(context, &(for_statement.into())).is_some_and(identity) {
28 | capture!(self, context, for_statement);
29 | }
30 | }
31 |
32 | // Investigate while loops to check for storage writes
33 | for while_statement in context.while_statements() {
34 | if changes_state(context, &(while_statement.into())).is_some_and(identity) {
35 | capture!(self, context, while_statement);
36 | }
37 | }
38 |
39 | // Investigate the do while loops to check for storage writes
40 | for do_while_statement in context.do_while_statements() {
41 | if changes_state(context, &(do_while_statement.into())).is_some_and(identity) {
42 | capture!(self, context, do_while_statement);
43 | }
44 | }
45 |
46 | Ok(!self.found_instances.is_empty())
47 | }
48 |
49 | fn severity(&self) -> IssueSeverity {
50 | IssueSeverity::Low
51 | }
52 |
53 | fn title(&self) -> String {
54 | String::from("Costly operations inside loop")
55 | }
56 |
57 | fn description(&self) -> String {
58 | String::from(
59 | "Invoking `SSTORE` operations in loops may waste gas. Use a local variable to hold the loop computation result.",
60 | )
61 | }
62 |
63 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
64 | self.found_instances.clone()
65 | }
66 |
67 | fn name(&self) -> String {
68 | IssueDetectorNamePool::CostlyLoop.to_string()
69 | }
70 | }
71 |
72 | fn changes_state(context: &WorkspaceContext, ast_node: &ASTNode) -> Option<bool> {
73 | // Now, investigate the function to see if there is scope for any state variable changes
74 | let callgraphs =
75 | CallGraphConsumer::get(context, &[ast_node], CallGraphDirection::Inward).ok()?;
76 |
77 | for callgraph in callgraphs {
78 | let mut tracker = StateVariableChangeTracker { state_var_has_changed: false, context };
79 | callgraph.accept(context, &mut tracker).ok()?;
80 | if tracker.state_var_has_changed {
81 | return Some(true);
82 | }
83 | }
84 | Some(false)
85 | }
86 |
87 | struct StateVariableChangeTracker<'a> {
88 | state_var_has_changed: bool,
89 | context: &'a WorkspaceContext,
90 | }
91 |
92 | impl CallGraphVisitor for StateVariableChangeTracker<'_> {
93 | fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
94 | if self.state_var_has_changed {
95 | return Ok(());
96 | }
97 | // Check for state variable changes
98 | let finder = ApproximateStorageChangeFinder::from(self.context, node);
99 | if finder.state_variables_have_been_manipulated() {
100 | self.state_var_has_changed = true;
101 | }
102 | Ok(())
103 | }
104 | }
105 |
106 | #[cfg(test)]
107 | mod costly_operations_inside_loops_tests {
108 |
109 | use crate::detect::{detector::IssueDetector, low::costly_loop::CostlyLoopDetector};
110 |
111 | #[test]
112 |
113 | fn test_costly_operations_inside_loops() {
114 | let context = crate::detect::test_utils::load_solidity_source_unit(
115 | "../tests/contract-playground/src/CostlyOperationsInsideLoops.sol",
116 | );
117 |
118 | let mut detector = CostlyLoopDetector::default();
119 | let found = detector.detect(&context).unwrap();
120 | assert!(found);
121 | assert_eq!(detector.instances().len(), 1);
122 | }
123 | }
124 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/pre_declared_variable_usage.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{
2 | collections::{BTreeMap, HashSet},
3 | error::Error,
4 | };
5 |
6 | use crate::ast::{ASTNode, NodeID};
7 |
8 | use crate::{
9 | capture,
10 | context::{
11 | browser::{ExtractIdentifiers, ExtractVariableDeclarations},
12 | workspace::WorkspaceContext,
13 | },
14 | detect::{
15 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
16 | helpers,
17 | },
18 | };
19 | use eyre::Result;
20 |
21 | // HOW TO USE THIS TEMPLATE:
22 | // 1. Copy this file and rename it to the snake_case version of the issue you are detecting.
23 | // 2. Rename the PreDeclaredLocalVariableUsageDetector struct and impl to your new issue name.
24 | // 3. Add this file and detector struct to the mod.rs file in the same directory.
25 | // 4. Implement the detect function to find instances of the issue.
26 |
27 | #[derive(Default)]
28 | pub struct PreDeclaredLocalVariableUsageDetector {
29 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
30 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
31 | found_instances: BTreeMap<(String, usize, String), NodeID>,
32 | }
33 |
34 | impl IssueDetector for PreDeclaredLocalVariableUsageDetector {
35 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
36 | // Since this is restricted to local variables, we examine each function independently
37 | for function in context.function_definitions().into_iter().filter(|&f| f.implemented) {
38 | let local_variable_declaration_ids = ExtractVariableDeclarations::from(function)
39 | .extracted
40 | .iter()
41 | .map(|vd| vd.id)
42 | .collect::<HashSet<_>>();
43 |
44 | let used_local_variables = ExtractIdentifiers::from(function).extracted;
45 |
46 | let used_local_variables = used_local_variables
47 | .iter()
48 | .filter(|identifier| {
49 | identifier.referenced_declaration.is_some_and(|referenced_declaration| {
50 | local_variable_declaration_ids.contains(&referenced_declaration)
51 | })
52 | })
53 | .collect::<HashSet<_>>();
54 |
55 | for used in used_local_variables {
56 | if let Some(id) = used.referenced_declaration
57 | && let Some(ASTNode::VariableDeclaration(variable_declaration)) =
58 | context.nodes.get(&id)
59 | {
60 | let used_offset = helpers::get_node_offset(&used.into());
61 | let declaration_offset = helpers::get_node_offset(&variable_declaration.into());
62 |
63 | if let (Some(used_offset), Some(declaration_offset)) =
64 | (used_offset, declaration_offset)
65 | && used_offset < declaration_offset
66 | {
67 | capture!(self, context, used);
68 | }
69 | }
70 | }
71 | }
72 |
73 | Ok(!self.found_instances.is_empty())
74 | }
75 |
76 | fn severity(&self) -> IssueSeverity {
77 | IssueSeverity::High
78 | }
79 |
80 | fn title(&self) -> String {
81 | String::from("Usage of variable before declaration")
82 | }
83 |
84 | fn description(&self) -> String {
85 | String::from("Declare the variable before using it to avoid unintended consequences.")
86 | }
87 |
88 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
89 | self.found_instances.clone()
90 | }
91 |
92 | fn name(&self) -> String {
93 | IssueDetectorNamePool::PreDeclaredLocalVariableUsage.to_string()
94 | }
95 | }
96 |
97 | #[cfg(test)]
98 | mod pre_declared_variable_usage_tests {
99 |
100 | use crate::detect::{
101 | detector::IssueDetector,
102 | high::pre_declared_variable_usage::PreDeclaredLocalVariableUsageDetector,
103 | };
104 |
105 | #[test]
106 |
107 | fn test_pre_declared_variable_usage() {
108 | let context = crate::detect::test_utils::load_solidity_source_unit(
109 | "../tests/contract-playground/src/PreDeclaredVarUsage.sol",
110 | );
111 |
112 | let mut detector = PreDeclaredLocalVariableUsageDetector::default();
113 | let found = detector.detect(&context).unwrap();
114 | assert!(found);
115 | assert_eq!(detector.instances().len(), 1);
116 | }
117 | }
118 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/strict_equality_contract_balance.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{Expression, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::workspace::WorkspaceContext,
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct DangerousStrictEqualityOnBalanceDetector {
14 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
15 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
16 | found_instances: BTreeMap<(String, usize, String), NodeID>,
17 | }
18 |
19 | impl IssueDetector for DangerousStrictEqualityOnBalanceDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | // When you have found an instance of the issue,
22 | // use the following macro to add it to `found_instances`:
23 | //
24 | // capture!(self, context, item);
25 |
26 | for binary_operation in context
27 | .binary_operations()
28 | .into_iter()
29 | .filter(|&op| op.operator == "==" || op.operator == "!=")
30 | {
31 | for expr in [
32 | binary_operation.left_expression.as_ref(),
33 | binary_operation.right_expression.as_ref(),
34 | ] {
35 | if let Expression::MemberAccess(member_access) = expr
36 | && member_access.member_name == "balance"
37 | && member_access.expression.as_ref().type_descriptions().is_some_and(
38 | |type_desc| {
39 | type_desc.type_string.as_ref().is_some_and(|type_string| {
40 | // For older solc versions when you say this.balance, "this" is
41 | // of type contract XXX
42 | type_string.starts_with("contract ")
43 | // In newers solidity versions, you say address(this).balance or payable(address(this)).balance
44 | || type_string == "address"
45 | || type_string == "address payable"
46 | })
47 | },
48 | )
49 | {
50 | capture!(self, context, binary_operation);
51 | }
52 | }
53 | }
54 |
55 | Ok(!self.found_instances.is_empty())
56 | }
57 |
58 | fn severity(&self) -> IssueSeverity {
59 | IssueSeverity::High
60 | }
61 |
62 | fn title(&self) -> String {
63 | String::from("Dangerous strict equality checks on contract balances")
64 | }
65 |
66 | fn description(&self) -> String {
67 | String::from(
68 | "A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality.",
69 | )
70 | }
71 |
72 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
73 | self.found_instances.clone()
74 | }
75 |
76 | fn name(&self) -> String {
77 | IssueDetectorNamePool::StrictEqualityContractBalance.to_string()
78 | }
79 | }
80 |
81 | #[cfg(test)]
82 | mod strict_equality_contract_balance_tests {
83 |
84 | use crate::detect::{
85 | detector::IssueDetector,
86 | high::strict_equality_contract_balance::DangerousStrictEqualityOnBalanceDetector,
87 | };
88 |
89 | #[test]
90 |
91 | fn test_strict_equality_contract_balance1() {
92 | let context = crate::detect::test_utils::load_solidity_source_unit(
93 | "../tests/contract-playground/src/DangerousStrictEquality1.sol",
94 | );
95 |
96 | let mut detector = DangerousStrictEqualityOnBalanceDetector::default();
97 | let found = detector.detect(&context).unwrap();
98 | assert!(found);
99 | assert_eq!(detector.instances().len(), 1);
100 | }
101 |
102 | #[test]
103 | fn test_strict_equality_contract_balance2() {
104 | let context = crate::detect::test_utils::load_solidity_source_unit(
105 | "../tests/contract-playground/src/DangerousStrictEquality2.sol",
106 | );
107 |
108 | let mut detector = DangerousStrictEqualityOnBalanceDetector::default();
109 | let found = detector.detect(&context).unwrap();
110 | assert!(found);
111 | assert_eq!(detector.instances().len(), 2);
112 | }
113 | }
114 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unused_state_variable.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{
2 | collections::{BTreeMap, BTreeSet},
3 | error::Error,
4 | };
5 |
6 | use crate::ast::{ASTNode, ContractKind, NodeID, NodeType, Visibility};
7 |
8 | use crate::{
9 | capture,
10 | context::{
11 | browser::{
12 | ExtractReferencedDeclarations, ExtractVariableDeclarations, GetClosestAncestorOfTypeX,
13 | },
14 | workspace::WorkspaceContext,
15 | },
16 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
17 | };
18 | use eyre::Result;
19 |
20 | #[derive(Default)]
21 | pub struct UnusedStateVariablesDetector {
22 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
23 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
24 | found_instances: BTreeMap<(String, usize, String), NodeID>,
25 | }
26 |
27 | impl IssueDetector for UnusedStateVariablesDetector {
28 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
29 | // Collect all referencedDeclaration IDs and StateVariableDeclarationIDs
30 | let mut all_referenced_declarations = BTreeSet::new();
31 | let mut all_state_variable_declarations = BTreeSet::new();
32 |
33 | for source_unit in context.source_units() {
34 | let referenced_declarations =
35 | ExtractReferencedDeclarations::from(source_unit).extracted;
36 | all_referenced_declarations.extend(referenced_declarations);
37 | let variable_declarations = ExtractVariableDeclarations::from(source_unit).extracted;
38 | all_state_variable_declarations.extend(
39 | variable_declarations
40 | .into_iter()
41 | .filter(|v| {
42 | v.state_variable
43 | && (v.visibility == Visibility::Private
44 | || v.visibility == Visibility::Internal)
45 | })
46 | .map(|v| v.id),
47 | )
48 | }
49 |
50 | // Now, retain only the ones that have not been referenced
51 | all_state_variable_declarations.retain(|v| !all_referenced_declarations.contains(v));
52 |
53 | for unused_state_var_id in all_state_variable_declarations {
54 | if let Some(node) = context.nodes.get(&unused_state_var_id) {
55 | if let Some(ASTNode::ContractDefinition(contract)) =
56 | node.closest_ancestor_of_type(context, NodeType::ContractDefinition)
57 | {
58 | // If this variable is defined inside a contract, make sure it's not an abstract
59 | // contract before capturing it
60 | if !contract.is_abstract && contract.kind == ContractKind::Contract {
61 | capture!(self, context, node);
62 | }
63 | } else {
64 | // Otherwise, just capture it !
65 | capture!(self, context, node);
66 | }
67 | }
68 | }
69 |
70 | Ok(!self.found_instances.is_empty())
71 | }
72 |
73 | fn severity(&self) -> IssueSeverity {
74 | IssueSeverity::Low
75 | }
76 |
77 | fn title(&self) -> String {
78 | String::from("Unused State Variable")
79 | }
80 |
81 | fn description(&self) -> String {
82 | String::from(
83 | "State variable appears to be unused. No analysis has been performed to see if any inline assembly \
84 | references it. Consider removing this unused variable.",
85 | )
86 | }
87 |
88 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
89 | self.found_instances.clone()
90 | }
91 |
92 | fn name(&self) -> String {
93 | format!("{}", IssueDetectorNamePool::UnusedStateVariable)
94 | }
95 | }
96 |
97 | #[cfg(test)]
98 | mod unused_detector_tests {
99 |
100 | use crate::detect::{
101 | detector::IssueDetector, low::unused_state_variable::UnusedStateVariablesDetector,
102 | };
103 |
104 | #[test]
105 |
106 | fn test_unused_state_variables() {
107 | let context = crate::detect::test_utils::load_solidity_source_unit(
108 | "../tests/contract-playground/src/UnusedStateVariables.sol",
109 | );
110 |
111 | let mut detector = UnusedStateVariablesDetector::default();
112 | let found = detector.detect(&context).unwrap();
113 | assert!(found);
114 | assert_eq!(detector.instances().len(), 4);
115 | }
116 | }
117 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/assert_state_change.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, convert::identity, error::Error};
2 |
3 | use crate::ast::{Expression, Identifier, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::workspace::WorkspaceContext,
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct AssertStateChangeDetector {
14 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
15 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
16 | found_instances: BTreeMap<(String, usize, String), NodeID>,
17 | }
18 |
19 | impl IssueDetector for AssertStateChangeDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | for function_call in context.function_calls() {
22 | if let Expression::Identifier(Identifier { name, .. }) =
23 | function_call.expression.as_ref()
24 | && name == "assert"
25 | && function_call.arguments_change_contract_state(context).is_some_and(identity)
26 | {
27 | capture!(self, context, function_call);
28 | }
29 | }
30 |
31 | Ok(!self.found_instances.is_empty())
32 | }
33 |
34 | fn severity(&self) -> IssueSeverity {
35 | IssueSeverity::Low
36 | }
37 |
38 | fn title(&self) -> String {
39 | String::from("State change in `assert()` statement")
40 | }
41 |
42 | fn description(&self) -> String {
43 | String::from(
44 | "An argument to `assert()` modifies the state. Use `require` for invariants modifying state.",
45 | )
46 | }
47 |
48 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
49 | self.found_instances.clone()
50 | }
51 |
52 | fn name(&self) -> String {
53 | format!("{}", IssueDetectorNamePool::AssertStateChange)
54 | }
55 | }
56 |
57 | mod assert_state_change_tracker {
58 | use crate::{
59 | ast::{ASTNode, FunctionCall},
60 | context::{
61 | browser::ApproximateStorageChangeFinder,
62 | graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
63 | workspace::WorkspaceContext,
64 | },
65 | };
66 |
67 | struct StateVariableChangeTracker<'a> {
68 | has_some_state_variable_changed: bool,
69 | context: &'a WorkspaceContext,
70 | }
71 |
72 | impl CallGraphVisitor for StateVariableChangeTracker<'_> {
73 | fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
74 | if self.has_some_state_variable_changed {
75 | return Ok(());
76 | }
77 | let finder = ApproximateStorageChangeFinder::from(self.context, node);
78 | if finder.state_variables_have_been_manipulated() {
79 | self.has_some_state_variable_changed = true;
80 | }
81 | Ok(())
82 | }
83 | }
84 |
85 | impl FunctionCall {
86 | pub fn arguments_change_contract_state(&self, context: &WorkspaceContext) -> Option<bool> {
87 | let arguments =
88 | self.arguments.clone().into_iter().map(|n| n.into()).collect::<Vec<ASTNode>>();
89 | let ast_nodes: &[&ASTNode] = &(arguments.iter().collect::<Vec<_>>());
90 |
91 | let callgraphs =
92 | CallGraphConsumer::get(context, ast_nodes, CallGraphDirection::Inward).ok()?;
93 |
94 | for callgraph in callgraphs {
95 | let mut tracker =
96 | StateVariableChangeTracker { has_some_state_variable_changed: false, context };
97 | callgraph.accept(context, &mut tracker).ok()?;
98 | if tracker.has_some_state_variable_changed {
99 | return Some(true);
100 | }
101 | }
102 |
103 | Some(false)
104 | }
105 | }
106 | }
107 |
108 | #[cfg(test)]
109 | mod assert_state_changes_tests {
110 |
111 | use crate::detect::{
112 | detector::IssueDetector, low::assert_state_change::AssertStateChangeDetector,
113 | };
114 |
115 | #[test]
116 |
117 | fn test_assert_state_change() {
118 | let context = crate::detect::test_utils::load_solidity_source_unit(
119 | "../tests/contract-playground/src/AssertStateChange.sol",
120 | );
121 |
122 | let mut detector = AssertStateChangeDetector::default();
123 | let found = detector.detect(&context).unwrap();
124 |
125 | assert!(found);
126 | assert_eq!(detector.instances().len(), 1);
127 | }
128 | }
129 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/msg_value_in_loops.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, convert::identity, error::Error};
2 |
3 | use crate::ast::{ASTNode, Expression, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::{
8 | browser::ExtractMemberAccesses,
9 | graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
10 | workspace::WorkspaceContext,
11 | },
12 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
13 | };
14 | use eyre::Result;
15 |
16 | #[derive(Default)]
17 | pub struct MsgValueUsedInLoopDetector {
18 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
19 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
20 | found_instances: BTreeMap<(String, usize, String), NodeID>,
21 | }
22 |
23 | impl IssueDetector for MsgValueUsedInLoopDetector {
24 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
25 | // Investigate for loops to check for usage of `msg.value`
26 | for for_statement in context.for_statements() {
27 | if uses_msg_value(context, &(for_statement.into())).is_some_and(identity) {
28 | capture!(self, context, for_statement);
29 | }
30 | }
31 |
32 | // Investigate while loops to check for usage of `msg.value`
33 | for while_statement in context.while_statements() {
34 | if uses_msg_value(context, &(while_statement.into())).is_some_and(identity) {
35 | capture!(self, context, while_statement);
36 | }
37 | }
38 |
39 | // Investigate the do while loops to check for usage of `msg.value`
40 | for do_while_statement in context.do_while_statements() {
41 | if uses_msg_value(context, &(do_while_statement.into())).is_some_and(identity) {
42 | capture!(self, context, do_while_statement);
43 | }
44 | }
45 |
46 | Ok(!self.found_instances.is_empty())
47 | }
48 |
49 | fn severity(&self) -> IssueSeverity {
50 | IssueSeverity::High
51 | }
52 |
53 | fn title(&self) -> String {
54 | String::from("Loop contains `msg.value`")
55 | }
56 |
57 | fn description(&self) -> String {
58 | String::from(
59 | "Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`.",
60 | )
61 | }
62 |
63 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
64 | self.found_instances.clone()
65 | }
66 |
67 | fn name(&self) -> String {
68 | IssueDetectorNamePool::MsgValueInLoop.to_string()
69 | }
70 | }
71 |
72 | fn uses_msg_value(context: &WorkspaceContext, ast_node: &ASTNode) -> Option<bool> {
73 | let callgraphs =
74 | CallGraphConsumer::get(context, &[ast_node], CallGraphDirection::Inward).ok()?;
75 |
76 | for callgraph in callgraphs {
77 | let mut tracker = MsgValueTracker::default();
78 | callgraph.accept(context, &mut tracker).ok()?;
79 | if tracker.has_msg_value {
80 | return Some(true);
81 | }
82 | }
83 | Some(false)
84 | }
85 |
86 | #[derive(Default)]
87 | struct MsgValueTracker {
88 | has_msg_value: bool,
89 | }
90 |
91 | impl CallGraphVisitor for MsgValueTracker {
92 | fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
93 | if !self.has_msg_value
94 | && ExtractMemberAccesses::from(node).extracted.iter().any(|member_access| {
95 | member_access.member_name == "value"
96 | && if let Expression::Identifier(identifier) = member_access.expression.as_ref()
97 | {
98 | identifier.name == "msg"
99 | } else {
100 | false
101 | }
102 | })
103 | {
104 | self.has_msg_value = true;
105 | }
106 |
107 | Ok(())
108 | }
109 | }
110 |
111 | #[cfg(test)]
112 | mod msg_value_in_loop_detector {
113 |
114 | use crate::detect::{
115 | detector::IssueDetector, high::msg_value_in_loops::MsgValueUsedInLoopDetector,
116 | };
117 |
118 | #[test]
119 |
120 | fn test_msg_value_in_loop() {
121 | let context = crate::detect::test_utils::load_solidity_source_unit(
122 | "../tests/contract-playground/src/MsgValueInLoop.sol",
123 | );
124 |
125 | let mut detector = MsgValueUsedInLoopDetector::default();
126 | let found = detector.detect(&context).unwrap();
127 | assert!(found);
128 | assert_eq!(detector.instances().len(), 4);
129 | }
130 | }
131 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/router/modifier_calls.rs:
--------------------------------------------------------------------------------
```rust
1 | use super::Router;
2 | use crate::{
3 | ast::{
4 | ASTNode, ContractDefinition, ContractKind, IdentifierOrIdentifierPath, ModifierDefinition,
5 | ModifierInvocation, NodeID, NodeType,
6 | },
7 | context::{browser::GetClosestAncestorOfTypeX, workspace::WorkspaceContext},
8 | };
9 | use std::collections::{HashMap, hash_map::Entry};
10 |
11 | impl Router {
12 | pub(super) fn _resolve_modifier_call<'a>(
13 | &self,
14 | context: &'a WorkspaceContext,
15 | base_contract: &'a ContractDefinition,
16 | modifier_call: &'a ModifierInvocation,
17 | ) -> Option<&'a ModifierDefinition> {
18 | // check if it's illegal base contract type
19 | if !base_contract.is_deployable_contract() {
20 | return None;
21 | }
22 |
23 | // check if it's illegal value - i.e function call that cannot be called from the base
24 | // contract must be discarded
25 | if let Some(ASTNode::ContractDefinition(caller_contract)) =
26 | modifier_call.closest_ancestor_of_type(context, NodeType::ContractDefinition)
27 | {
28 | if caller_contract.kind == ContractKind::Contract
29 | && !caller_contract.is_in(context, base_contract)
30 | {
31 | return None;
32 | } else if caller_contract.kind == ContractKind::Library {
33 | // If an internal modifier call happens from a library, suspect cannot be overridden
34 | // As of now, libraries do not have inheritance
35 | //
36 | // NOTE: for this case, we don't check that the modifier call from library can
37 | // actually happen and is trigger(able) by the base contract.
38 | let aux_modifier = modifier_call.suspected_target_modifier(context)?;
39 | return Some(aux_modifier);
40 | }
41 | }
42 |
43 | self.perform_mc_lookup_through_inheritance_tree_and_fallback_to_suspect(
44 | context,
45 | base_contract,
46 | modifier_call,
47 | )
48 | }
49 |
50 | fn perform_mc_lookup_through_inheritance_tree_and_fallback_to_suspect<'a>(
51 | &self,
52 | context: &'a WorkspaceContext,
53 | base_contract: &'a ContractDefinition,
54 | modifier_call: &'a ModifierInvocation,
55 | ) -> Option<&'a ModifierDefinition> {
56 | let aux_modifier = modifier_call.suspected_target_modifier(context)?;
57 | let selectorish = aux_modifier.selectorish();
58 | let base_index = self.modifier_calls.get(&base_contract.id)?;
59 |
60 | let resolve = |starting_point: &ContractDefinition| -> Option<&ModifierDefinition> {
61 | let starting_point = starting_point.id;
62 | let lookup_index = base_index.routes.get(&starting_point)?;
63 | match lookup_index.get(&selectorish) {
64 | Some(modifier_id) => match context.nodes.get(modifier_id) {
65 | Some(ASTNode::ModifierDefinition(modifier_def)) => Some(modifier_def),
66 | _ => None,
67 | },
68 | // if not found in lookup fallback to aux function (suspect function)
69 | None => Some(aux_modifier),
70 | }
71 | };
72 |
73 | // TODO: Investigate when other enumeration can be triggered.
74 | if let IdentifierOrIdentifierPath::IdentifierPath(p) = &modifier_call.modifier_name {
75 | // Ex: `B.modify` is a full path so then the suspected target must be right.
76 | if p.name.contains('.') {
77 | return Some(aux_modifier);
78 | }
79 | // Ex: `modify`
80 | return resolve(base_contract);
81 | }
82 |
83 | None
84 | }
85 | }
86 |
87 | pub(super) fn build_mc_router_for_contract(
88 | context: &WorkspaceContext,
89 | base_contract: &ContractDefinition,
90 | ) -> HashMap<NodeID, HashMap<String, NodeID>> {
91 | let c3 = base_contract.c3(context).collect::<Vec<_>>();
92 | let mut base_routes = HashMap::new();
93 | for (idx, starting_point) in c3.iter().enumerate() {
94 | let mut routes = HashMap::new();
95 | for contract in c3.iter().skip(idx) {
96 | for modifier in contract.modifier_definitions() {
97 | if let Entry::Vacant(e) = routes.entry(modifier.selectorish()) {
98 | e.insert(modifier.id);
99 | }
100 | }
101 | }
102 | base_routes.insert(starting_point.id, routes);
103 | }
104 | base_routes
105 | }
106 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/delete_nested_mapping.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{
4 | ASTNode, Expression, Identifier, IndexAccess, Mapping, NodeID, TypeName, UserDefinedTypeName,
5 | VariableDeclaration,
6 | };
7 |
8 | use crate::{
9 | capture,
10 | context::workspace::WorkspaceContext,
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct DeletionNestedMappingDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for DeletionNestedMappingDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | for delete_operation in
25 | context.unary_operations().into_iter().filter(|op| op.operator == "delete")
26 | {
27 | if let Expression::IndexAccess(IndexAccess { base_expression, .. }) =
28 | delete_operation.sub_expression.as_ref()
29 | && let Expression::Identifier(Identifier {
30 | referenced_declaration: Some(referenced_id),
31 | type_descriptions,
32 | ..
33 | }) = base_expression.as_ref()
34 | {
35 | // Check if we're deleting a value from mapping
36 | if type_descriptions
37 | .type_string
38 | .as_ref()
39 | .is_some_and(|type_string| type_string.starts_with("mapping"))
40 | {
41 | // Check if the value in the mapping is of type struct that has a member
42 | // which is also a mapping
43 | if let Some(ASTNode::VariableDeclaration(VariableDeclaration {
44 | type_name: Some(TypeName::Mapping(Mapping { value_type, .. })),
45 | ..
46 | })) = context.nodes.get(referenced_id)
47 | && let TypeName::UserDefinedTypeName(UserDefinedTypeName {
48 | referenced_declaration,
49 | ..
50 | }) = value_type.as_ref()
51 | && let Some(ASTNode::StructDefinition(structure)) =
52 | context.nodes.get(referenced_declaration)
53 | {
54 | // Check that a member of a struct is of type mapping
55 | if structure.members.iter().any(|member| {
56 | member
57 | .type_descriptions
58 | .type_string
59 | .as_ref()
60 | .is_some_and(|type_string| type_string.starts_with("mapping"))
61 | }) {
62 | capture!(self, context, delete_operation);
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
69 | Ok(!self.found_instances.is_empty())
70 | }
71 |
72 | fn severity(&self) -> IssueSeverity {
73 | IssueSeverity::High
74 | }
75 |
76 | fn title(&self) -> String {
77 | String::from("Deletion from a nested mapping")
78 | }
79 |
80 | fn description(&self) -> String {
81 | String::from(
82 | "A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract.",
83 | )
84 | }
85 |
86 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
87 | self.found_instances.clone()
88 | }
89 |
90 | fn name(&self) -> String {
91 | IssueDetectorNamePool::DeleteNestedMapping.to_string()
92 | }
93 | }
94 |
95 | #[cfg(test)]
96 | mod deletion_nested_mapping_tests {
97 |
98 | use crate::detect::{
99 | detector::IssueDetector, high::delete_nested_mapping::DeletionNestedMappingDetector,
100 | };
101 |
102 | #[test]
103 |
104 | fn test_deletion_nested_mapping() {
105 | let context = crate::detect::test_utils::load_solidity_source_unit(
106 | "../tests/contract-playground/src/DeletionNestedMappingStructureContract.sol",
107 | );
108 |
109 | let mut detector = DeletionNestedMappingDetector::default();
110 | let found = detector.detect(&context).unwrap();
111 | assert!(found);
112 | assert_eq!(detector.instances().len(), 1);
113 | }
114 | }
115 |
```
--------------------------------------------------------------------------------
/aderyn_driver/src/interface/lsp.rs:
--------------------------------------------------------------------------------
```rust
1 | use aderyn_core::report::*;
2 | use std::{collections::BTreeMap, path::Path};
3 | use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url};
4 |
5 | /// Report structure that is tailored to aid LSP
6 | pub struct LspReport {
7 | pub high_issues: HighIssues,
8 | pub low_issues: LowIssues,
9 | pub diagnostics: BTreeMap<Url, Vec<Diagnostic>>,
10 | }
11 |
12 | impl LspReport {
13 | pub fn from(low_issues: LowIssues, high_issues: HighIssues, root_rel_path: &Path) -> Self {
14 | fn create_diagnostic_from_issue(
15 | issue_body: &IssueBody,
16 | instance: &IssueInstance,
17 | severity: DiagnosticSeverity,
18 | root_rel_path: &Path,
19 | ) -> Option<(Url, Diagnostic)> {
20 | // Line number
21 | let line_no = instance.line_no.checked_sub(1)?;
22 |
23 | // Character position and range from the start of the line number
24 | let (pos_start, pos_range) = instance.src_char2.split_once(':')?;
25 | let pos_start = pos_start.parse::<u32>().unwrap_or_default().checked_sub(1)?;
26 | let pos_range = pos_range.parse::<u32>().unwrap_or_default();
27 |
28 | // Craft the diagnostic message
29 | let mut message = format!("Title: {}\n", issue_body.title);
30 |
31 | if !issue_body.description.is_empty() {
32 | message.push_str(&format!("\nDescription: {}\n", issue_body.description));
33 | }
34 |
35 | if let Some(hint) = instance.hint.clone()
36 | && !hint.is_empty()
37 | {
38 | message.push_str(&format!("\nHint: {}\n", hint));
39 | }
40 |
41 | message.push_str(&format!(
42 | "\nTo ignore this warning, add:\n\n// aderyn-ignore-next-line({})\n\nor mark as false positive:\n\n// aderyn-fp-next-line({})\n\n",
43 | issue_body.detector_name,
44 | issue_body.detector_name,
45 | ));
46 |
47 | // Make the diagnostic that LSP can understand
48 | let diagnostic = Diagnostic {
49 | range: Range {
50 | start: Position { line: line_no as u32, character: pos_start },
51 | end: Position { line: line_no as u32, character: pos_start + pos_range },
52 | },
53 | severity: Some(severity),
54 | message,
55 | code: None,
56 | code_description: None,
57 | source: Some("Aderyn".to_string()),
58 | related_information: None,
59 | tags: None,
60 | data: None,
61 | };
62 | let mut full_contract_path = root_rel_path.to_path_buf();
63 | full_contract_path.push(instance.contract_path.clone());
64 | let full_contract_path = full_contract_path.canonicalize().ok()?;
65 | let full_contract_path_string = full_contract_path.to_string_lossy().to_string();
66 | let file_uri = Url::parse(&format!("file://{}", &full_contract_path_string)).ok()?;
67 |
68 | Some((file_uri, diagnostic))
69 | }
70 |
71 | let mut diagnostics = BTreeMap::new();
72 |
73 | for issue_body in &high_issues.issues {
74 | for instance in &issue_body.instances {
75 | let Some((file_url, diagnostic)) = create_diagnostic_from_issue(
76 | issue_body,
77 | instance,
78 | DiagnosticSeverity::WARNING,
79 | root_rel_path,
80 | ) else {
81 | continue;
82 | };
83 |
84 | let file_diagnostics: &mut Vec<Diagnostic> =
85 | diagnostics.entry(file_url).or_default();
86 |
87 | file_diagnostics.push(diagnostic);
88 | }
89 | }
90 | for issue_body in &low_issues.issues {
91 | for instance in &issue_body.instances {
92 | let Some((file_url, diagnostic)) = create_diagnostic_from_issue(
93 | issue_body,
94 | instance,
95 | DiagnosticSeverity::INFORMATION,
96 | root_rel_path,
97 | ) else {
98 | continue;
99 | };
100 |
101 | let file_diagnostics: &mut Vec<Diagnostic> =
102 | diagnostics.entry(file_url).or_default();
103 |
104 | file_diagnostics.push(diagnostic);
105 | }
106 | }
107 | Self { low_issues, high_issues, diagnostics }
108 | }
109 | }
110 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/eth_send_unchecked_address.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::NodeID;
4 |
5 | use crate::{
6 | capture,
7 | context::{
8 | browser::ExtractModifierInvocations,
9 | graph::CallGraphVisitor,
10 | workspace::{ASTNode, WorkspaceContext},
11 | },
12 | detect::{
13 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
14 | helpers,
15 | },
16 | };
17 | use eyre::Result;
18 |
19 | #[derive(Default)]
20 | pub struct SendEtherNoChecksDetector {
21 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
22 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
23 | found_instances: BTreeMap<(String, usize, String), NodeID>,
24 | }
25 |
26 | impl IssueDetector for SendEtherNoChecksDetector {
27 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
28 | for (func, callgraphs) in context.entrypoints_with_callgraphs() {
29 | for callgraph in callgraphs {
30 | let mut tracker = AddressChecksAndCallWithValueTracker::default();
31 | callgraph.accept(context, &mut tracker)?;
32 |
33 | // Hacky way to check if the modifier is a know msg.sender checking modifier
34 | // This is because our Callgraph doesn't navigate inside contracts that are outside
35 | // the scope, this includes imported contracts.
36 | let has_oz_modifier =
37 | ExtractModifierInvocations::from(func).extracted.iter().any(|invocation| {
38 | invocation.modifier_name.name().contains("onlyRole")
39 | || invocation.modifier_name.name() == "onlyOwner"
40 | || invocation.modifier_name.name() == "requiresAuth"
41 | });
42 |
43 | if tracker.sends_native_eth
44 | && !tracker.has_binary_checks_on_some_address
45 | && !has_oz_modifier
46 | {
47 | capture!(self, context, func);
48 | }
49 | }
50 | }
51 |
52 | Ok(!self.found_instances.is_empty())
53 | }
54 |
55 | fn severity(&self) -> IssueSeverity {
56 | IssueSeverity::High
57 | }
58 |
59 | fn title(&self) -> String {
60 | String::from("ETH transferred without address checks")
61 | }
62 |
63 | fn description(&self) -> String {
64 | String::from(
65 | "Consider introducing checks for `msg.sender` to ensure the recipient of the money is as intended.",
66 | )
67 | }
68 |
69 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
70 | self.found_instances.clone()
71 | }
72 |
73 | fn name(&self) -> String {
74 | IssueDetectorNamePool::EthSendUncheckedAddress.to_string()
75 | }
76 | }
77 |
78 | #[derive(Default)]
79 | pub struct AddressChecksAndCallWithValueTracker {
80 | pub has_binary_checks_on_some_address: bool,
81 | pub sends_native_eth: bool,
82 | }
83 |
84 | impl CallGraphVisitor for AddressChecksAndCallWithValueTracker {
85 | fn visit_any(&mut self, node: &ASTNode) -> eyre::Result<()> {
86 | if !self.has_binary_checks_on_some_address
87 | && helpers::has_binary_checks_on_some_address(node)
88 | {
89 | self.has_binary_checks_on_some_address = true;
90 | }
91 | if !self.sends_native_eth && helpers::has_calls_that_sends_native_eth(node) {
92 | self.sends_native_eth = true;
93 | }
94 | eyre::Ok(())
95 | }
96 | }
97 |
98 | #[cfg(test)]
99 | mod send_ether_no_checks_detector_tests {
100 |
101 | use crate::detect::{
102 | detector::IssueDetector, high::eth_send_unchecked_address::SendEtherNoChecksDetector,
103 | };
104 |
105 | #[test]
106 | fn test_send_ether_no_checks_lib_import() {
107 | let context = crate::detect::test_utils::load_solidity_source_unit(
108 | "../tests/contract-playground/src/SendEtherNoChecksLibImport.sol",
109 | );
110 |
111 | let mut detector = SendEtherNoChecksDetector::default();
112 | let found = detector.detect(&context).unwrap();
113 | assert!(!found);
114 | assert_eq!(detector.instances().len(), 0);
115 | }
116 |
117 | #[test]
118 | fn test_send_ether_no_checks() {
119 | let context = crate::detect::test_utils::load_solidity_source_unit(
120 | "../tests/contract-playground/src/SendEtherNoChecks.sol",
121 | );
122 |
123 | let mut detector = SendEtherNoChecksDetector::default();
124 | let found = detector.detect(&context).unwrap();
125 | assert!(found);
126 | assert_eq!(detector.instances().len(), 3);
127 | }
128 | }
129 |
```
--------------------------------------------------------------------------------
/tests/hardhat-js-playground/test/Lock.js:
--------------------------------------------------------------------------------
```javascript
1 | const {
2 | time,
3 | loadFixture,
4 | } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
5 | const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
6 | const { expect } = require("chai");
7 |
8 | describe("Lock", function () {
9 | // We define a fixture to reuse the same setup in every test.
10 | // We use loadFixture to run this setup once, snapshot that state,
11 | // and reset Hardhat Network to that snapshot in every test.
12 | async function deployOneYearLockFixture() {
13 | const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
14 | const ONE_GWEI = 1_000_000_000;
15 |
16 | const lockedAmount = ONE_GWEI;
17 | const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
18 |
19 | // Contracts are deployed using the first signer/account by default
20 | const [owner, otherAccount] = await ethers.getSigners();
21 |
22 | const Lock = await ethers.getContractFactory("Lock");
23 | const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
24 |
25 | return { lock, unlockTime, lockedAmount, owner, otherAccount };
26 | }
27 |
28 | describe("Deployment", function () {
29 | it("Should set the right unlockTime", async function () {
30 | const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);
31 |
32 | expect(await lock.unlockTime()).to.equal(unlockTime);
33 | });
34 |
35 | it("Should set the right owner", async function () {
36 | const { lock, owner } = await loadFixture(deployOneYearLockFixture);
37 |
38 | expect(await lock.owner()).to.equal(owner.address);
39 | });
40 |
41 | it("Should receive and store the funds to lock", async function () {
42 | const { lock, lockedAmount } = await loadFixture(
43 | deployOneYearLockFixture
44 | );
45 |
46 | expect(await ethers.provider.getBalance(lock.target)).to.equal(
47 | lockedAmount
48 | );
49 | });
50 |
51 | it("Should fail if the unlockTime is not in the future", async function () {
52 | // We don't use the fixture here because we want a different deployment
53 | const latestTime = await time.latest();
54 | const Lock = await ethers.getContractFactory("Lock");
55 | await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
56 | "Unlock time should be in the future"
57 | );
58 | });
59 | });
60 |
61 | describe("Withdrawals", function () {
62 | describe("Validations", function () {
63 | it("Should revert with the right error if called too soon", async function () {
64 | const { lock } = await loadFixture(deployOneYearLockFixture);
65 |
66 | await expect(lock.withdraw()).to.be.revertedWith(
67 | "You can't withdraw yet"
68 | );
69 | });
70 |
71 | it("Should revert with the right error if called from another account", async function () {
72 | const { lock, unlockTime, otherAccount } = await loadFixture(
73 | deployOneYearLockFixture
74 | );
75 |
76 | // We can increase the time in Hardhat Network
77 | await time.increaseTo(unlockTime);
78 |
79 | // We use lock.connect() to send a transaction from another account
80 | await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
81 | "You aren't the owner"
82 | );
83 | });
84 |
85 | it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
86 | const { lock, unlockTime } = await loadFixture(
87 | deployOneYearLockFixture
88 | );
89 |
90 | // Transactions are sent using the first signer by default
91 | await time.increaseTo(unlockTime);
92 |
93 | await expect(lock.withdraw()).not.to.be.reverted;
94 | });
95 | });
96 |
97 | describe("Events", function () {
98 | it("Should emit an event on withdrawals", async function () {
99 | const { lock, unlockTime, lockedAmount } = await loadFixture(
100 | deployOneYearLockFixture
101 | );
102 |
103 | await time.increaseTo(unlockTime);
104 |
105 | await expect(lock.withdraw())
106 | .to.emit(lock, "Withdrawal")
107 | .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
108 | });
109 | });
110 |
111 | describe("Transfers", function () {
112 | it("Should transfer the funds to the owner", async function () {
113 | const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
114 | deployOneYearLockFixture
115 | );
116 |
117 | await time.increaseTo(unlockTime);
118 |
119 | await expect(lock.withdraw()).to.changeEtherBalances(
120 | [owner, lock],
121 | [lockedAmount, -lockedAmount]
122 | );
123 | });
124 | });
125 | });
126 | });
127 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/ast/impls/node/contracts.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{ast::*, visitor::ast_visitor::*};
2 | use eyre::Result;
3 |
4 | impl Node for ContractDefinitionNode {
5 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
6 | match self {
7 | ContractDefinitionNode::UsingForDirective(using_for_directive) => {
8 | using_for_directive.accept(visitor)
9 | }
10 | ContractDefinitionNode::StructDefinition(struct_definition) => {
11 | struct_definition.accept(visitor)
12 | }
13 | ContractDefinitionNode::EnumDefinition(enum_definition) => {
14 | enum_definition.accept(visitor)
15 | }
16 | ContractDefinitionNode::VariableDeclaration(variable_declaration) => {
17 | variable_declaration.accept(visitor)
18 | }
19 | ContractDefinitionNode::EventDefinition(event_definition) => {
20 | event_definition.accept(visitor)
21 | }
22 | ContractDefinitionNode::FunctionDefinition(function_definition) => {
23 | function_definition.accept(visitor)
24 | }
25 | ContractDefinitionNode::ModifierDefinition(modifier_definition) => {
26 | modifier_definition.accept(visitor)
27 | }
28 | ContractDefinitionNode::ErrorDefinition(error_definition) => {
29 | error_definition.accept(visitor)
30 | }
31 | ContractDefinitionNode::UserDefinedValueTypeDefinition(
32 | user_defined_value_type_definition,
33 | ) => user_defined_value_type_definition.accept(visitor),
34 | }
35 | }
36 |
37 | fn accept_id(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
38 | visitor.visit_node_id(self.get_node_id())?;
39 | Ok(())
40 | }
41 | }
42 |
43 | impl Node for InheritanceSpecifier {
44 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
45 | if visitor.visit_inheritance_specifier(self)? {
46 | match &self.base_name {
47 | UserDefinedTypeNameOrIdentifierPath::UserDefinedTypeName(type_name) => {
48 | type_name.accept(visitor)?
49 | }
50 | UserDefinedTypeNameOrIdentifierPath::IdentifierPath(identifier_path) => {
51 | identifier_path.accept(visitor)?;
52 | }
53 | };
54 | if self.arguments.is_some() {
55 | list_accept(self.arguments.as_ref().unwrap(), visitor)?;
56 | }
57 | }
58 | self.accept_metadata(visitor)?;
59 | visitor.end_visit_inheritance_specifier(self)
60 | }
61 | fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
62 | if let Some(base_name_id) = self.base_name.get_node_id() {
63 | visitor.visit_immediate_children(self.id, vec![base_name_id])?;
64 | }
65 | let mut argument_ids: Vec<NodeID> = vec![];
66 | if let Some(arguments) = &self.arguments {
67 | for expression in arguments {
68 | let node_id = expression.get_node_id();
69 | if let Some(node_id) = node_id {
70 | argument_ids.push(node_id)
71 | }
72 | }
73 | }
74 | visitor.visit_immediate_children(self.id, argument_ids)?;
75 | Ok(())
76 | }
77 | macros::accept_id!();
78 | }
79 |
80 | impl Node for ContractDefinition {
81 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
82 | if visitor.visit_contract_definition(self)? {
83 | if self.documentation.is_some() {
84 | self.documentation.as_ref().unwrap().accept(visitor)?;
85 | }
86 | list_accept(&self.base_contracts, visitor)?;
87 | list_accept(&self.nodes, visitor)?;
88 | }
89 | self.accept_metadata(visitor)?;
90 | visitor.end_visit_contract_definition(self)
91 | }
92 |
93 | fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
94 | // TODO: Skipping documentation for now
95 | let mut base_contracts_ids = vec![];
96 | for base_contract in &self.base_contracts {
97 | base_contracts_ids.push(base_contract.id);
98 | }
99 | visitor.visit_immediate_children(self.id, base_contracts_ids)?;
100 | let mut node_ids = vec![];
101 | for node in &self.nodes {
102 | if let Some(node_id) = node.get_node_id() {
103 | node_ids.push(node_id);
104 | }
105 | }
106 | visitor.visit_immediate_children(self.id, node_ids)?;
107 | Ok(())
108 | }
109 |
110 | macros::accept_id!();
111 | }
112 |
```
--------------------------------------------------------------------------------
/reports/adhoc-sol-files-highs-only-report.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "files_summary": {
3 | "total_source_units": 20,
4 | "total_sloc": 245
5 | },
6 | "files_details": {
7 | "files_details": [
8 | {
9 | "file_path": "Counter.sol",
10 | "n_sloc": 20
11 | },
12 | {
13 | "file_path": "DemoASTNodes.sol",
14 | "n_sloc": 31
15 | },
16 | {
17 | "file_path": "Helper.sol",
18 | "n_sloc": 8
19 | },
20 | {
21 | "file_path": "InconsistentUints.sol",
22 | "n_sloc": 17
23 | },
24 | {
25 | "file_path": "InternalFunctions.sol",
26 | "n_sloc": 22
27 | },
28 | {
29 | "file_path": "OnceModifierExample.sol",
30 | "n_sloc": 8
31 | },
32 | {
33 | "file_path": "StateVariables.sol",
34 | "n_sloc": 58
35 | },
36 | {
37 | "file_path": "inheritance/ExtendedInheritance.sol",
38 | "n_sloc": 17
39 | },
40 | {
41 | "file_path": "inheritance/IContractInheritance.sol",
42 | "n_sloc": 4
43 | },
44 | {
45 | "file_path": "inheritance/InheritanceBase.sol",
46 | "n_sloc": 8
47 | },
48 | {
49 | "file_path": "multiple-versions/0.4/A.sol",
50 | "n_sloc": 5
51 | },
52 | {
53 | "file_path": "multiple-versions/0.4/B.sol",
54 | "n_sloc": 5
55 | },
56 | {
57 | "file_path": "multiple-versions/0.5/A.sol",
58 | "n_sloc": 5
59 | },
60 | {
61 | "file_path": "multiple-versions/0.5/B.sol",
62 | "n_sloc": 7
63 | },
64 | {
65 | "file_path": "multiple-versions/0.6/A.sol",
66 | "n_sloc": 5
67 | },
68 | {
69 | "file_path": "multiple-versions/0.6/B.sol",
70 | "n_sloc": 5
71 | },
72 | {
73 | "file_path": "multiple-versions/0.7/A.sol",
74 | "n_sloc": 5
75 | },
76 | {
77 | "file_path": "multiple-versions/0.7/B.sol",
78 | "n_sloc": 5
79 | },
80 | {
81 | "file_path": "multiple-versions/0.8/A.sol",
82 | "n_sloc": 5
83 | },
84 | {
85 | "file_path": "multiple-versions/0.8/B.sol",
86 | "n_sloc": 5
87 | }
88 | ]
89 | },
90 | "issue_count": {
91 | "high": 2,
92 | "low": 0
93 | },
94 | "high_issues": {
95 | "issues": [
96 | {
97 | "title": "`delegatecall` to an Arbitrary Address",
98 | "description": "Making a `delegatecall` to an arbitrary address without any checks is dangerous. Consider adding requirements on the target address.",
99 | "detector_name": "delegate-call-unchecked-address",
100 | "instances": [
101 | {
102 | "contract_path": "inheritance/ExtendedInheritance.sol",
103 | "line_no": 14,
104 | "src": "391:15",
105 | "src_char": "391:15"
106 | }
107 | ]
108 | },
109 | {
110 | "title": "Unchecked Low level calls",
111 | "description": "The return value of the low-level call is not checked, so if the call fails, the Ether will be locked in the contract. If the low level is used to prevent blocking operations, consider logging failed calls. Ensure that the return value of a low-level call is checked or logged.",
112 | "detector_name": "unchecked-low-level-call",
113 | "instances": [
114 | {
115 | "contract_path": "inheritance/ExtendedInheritance.sol",
116 | "line_no": 16,
117 | "src": "488:71",
118 | "src_char": "488:71"
119 | }
120 | ]
121 | }
122 | ]
123 | },
124 | "low_issues": {
125 | "issues": []
126 | },
127 | "detectors_used": [
128 | "abi-encode-packed-hash-collision",
129 | "arbitrary-transfer-from",
130 | "unprotected-initializer",
131 | "unsafe-casting",
132 | "enumerable-loop-removal",
133 | "experimental-encoder",
134 | "incorrect-shift-order",
135 | "storage-array-memory-edit",
136 | "multiple-constructors",
137 | "reused-contract-name",
138 | "nested-struct-in-mapping",
139 | "selfdestruct",
140 | "dynamic-array-length-assignment",
141 | "incorrect-caret-operator",
142 | "yul-return",
143 | "state-variable-shadowing",
144 | "unchecked-send",
145 | "misused-boolean",
146 | "eth-send-unchecked-address",
147 | "delegate-call-unchecked-address",
148 | "tautological-compare",
149 | "rtlo",
150 | "dangerous-unary-operator",
151 | "tautology-or-contradiction",
152 | "strict-equality-contract-balance",
153 | "signed-integer-storage-array",
154 | "weak-randomness",
155 | "pre-declared-local-variable-usage",
156 | "delete-nested-mapping",
157 | "tx-origin-used-for-auth",
158 | "msg-value-in-loop",
159 | "contract-locks-ether",
160 | "incorrect-erc721-interface",
161 | "incorrect-erc20-interface",
162 | "out-of-order-retryable",
163 | "constant-function-changes-state",
164 | "function-selector-collision",
165 | "unchecked-low-level-call",
166 | "reentrancy-state-change"
167 | ]
168 | }
```