This is page 7 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/context/mcp/project_overview/tool.rs:
--------------------------------------------------------------------------------
```rust
1 | use super::render;
2 | use crate::context::{
3 | macros::mcp_success,
4 | mcp::{
5 | MCPToolNamePool, ModelContextProtocolState, ModelContextProtocolTool,
6 | project_overview::render::*,
7 | },
8 | };
9 | use indoc::indoc;
10 | use rmcp::{ErrorData as McpError, handler::server::wrapper::Parameters, model::CallToolResult};
11 | use std::{path::PathBuf, str::FromStr, sync::Arc};
12 |
13 | #[derive(Clone)]
14 | pub struct ProjectOverviewTool {
15 | state: Arc<ModelContextProtocolState>,
16 | }
17 |
18 | impl ModelContextProtocolTool for ProjectOverviewTool {
19 | type Input = rmcp::model::EmptyObject;
20 |
21 | fn new(state: Arc<ModelContextProtocolState>) -> Self {
22 | Self { state }
23 | }
24 |
25 | fn name(&self) -> String {
26 | MCPToolNamePool::AderynGetProjectOverview.to_string()
27 | }
28 |
29 | fn description(&self) -> String {
30 | indoc! {
31 | "It returns project configuration such as the root directory, source directory \
32 | (where the contracts are kept), 3rd party libraries, remappings, list of source \
33 | files, user preference for included and excluded files, etc."
34 | }
35 | .to_string()
36 | }
37 |
38 | fn execute(&self, _input: Parameters<Self::Input>) -> Result<CallToolResult, McpError> {
39 | // remappings
40 | let mut remapping_strings = vec![];
41 | for r in self.state.project_config.project_paths.remappings.iter() {
42 | remapping_strings.push(r.to_string());
43 | }
44 | // compilation units
45 | let mut compilation_units = vec![];
46 | for context in self.state.contexts.iter() {
47 | let mut file_entries = vec![];
48 | let mut included_count = 0;
49 | for file in context.src_filepaths.iter() {
50 | let file_entry;
51 | if context.included.contains(&PathBuf::from_str(file).unwrap()) {
52 | file_entry = FileEntryBuilder::default()
53 | .path(file.clone())
54 | .included(true)
55 | .build()
56 | .expect("failed to build file entry");
57 | included_count += 1;
58 | } else {
59 | file_entry = FileEntryBuilder::default()
60 | .path(file.clone())
61 | .included(false)
62 | .build()
63 | .expect("failed to build file entry");
64 | }
65 | file_entries.push(file_entry);
66 | }
67 | let compilation_unit = CompilationUnitBuilder::default()
68 | .files(file_entries)
69 | .included_count(included_count)
70 | .build()
71 | .expect("failed to build compilation unit");
72 |
73 | compilation_units.push(compilation_unit);
74 | }
75 |
76 | let project_overview = render::ProjectOverviewBuilder::default()
77 | .root(self.state.root_path.to_string_lossy().to_string())
78 | .source(self.state.project_config.project_paths.sources.to_string_lossy().to_string())
79 | .remappings(remapping_strings)
80 | .compilation_units(compilation_units)
81 | .build()
82 | .expect("failed to build project overview");
83 |
84 | mcp_success!(project_overview)
85 | }
86 | }
87 |
```
--------------------------------------------------------------------------------
/aderyn_core/tests/common/ancestral_line.rs:
--------------------------------------------------------------------------------
```rust
1 | #![allow(clippy::collapsible_match)]
2 | use std::{collections::BTreeMap, error::Error};
3 |
4 | use aderyn_core::{
5 | ast::NodeID,
6 | capture,
7 | context::{
8 | browser::{GetAncestralLine, SortNodeReferencesToSequence},
9 | workspace::{ASTNode, WorkspaceContext},
10 | },
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct AncestralLineDemonstrator {
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 | /*
23 |
24 | In ParentChainContract.sol, there is only 1 assignment done. The goal is to capture it first, second and third parent
25 | */
26 |
27 | impl IssueDetector for AncestralLineDemonstrator {
28 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
29 | for assignment in context.assignments() {
30 | capture!(self, context, assignment);
31 |
32 | if let Some(parent_chain) = assignment.ancestral_line(context) {
33 | if let ASTNode::ExpressionStatement(_) = parent_chain[1] {
34 | capture!(self, context, parent_chain[1]);
35 | // NOTE: Above capture is redundant because assignment also shares the same
36 | // location (size and offset) with the expression statement. Therefore the
37 | // number of found_instances doesn't increase here
38 | }
39 | if let ASTNode::Block(_) = parent_chain[2] {
40 | capture!(self, context, parent_chain[2]);
41 | }
42 | if let ASTNode::ForStatement(_) = parent_chain[3] {
43 | capture!(self, context, parent_chain[3]);
44 | }
45 | if let ASTNode::Block(block) = parent_chain[4] {
46 | capture!(self, context, block);
47 | }
48 | }
49 |
50 | if let Some(mut parent_chain) = assignment.ancestral_line(context) {
51 | let _sorted_chain = parent_chain.sort_by_src_position(context).unwrap();
52 |
53 | //println!("Sorted Chain");
54 | //for i in &sorted_chain[..sorted_chain.len() - 2] {
55 | // print!("{:?}, ", i.node_type());
56 | //}
57 | parent_chain.reverse();
58 |
59 | //println!("Reverse parent chain");
60 | //for i in &parent_chain[..parent_chain.len() - 2] {
61 | // print!("{:?}, ", i.node_type());
62 | //}
63 | // assert_eq!(sorted_chain, parent_chain);
64 | }
65 | }
66 |
67 | Ok(!self.found_instances.is_empty())
68 | }
69 |
70 | fn severity(&self) -> IssueSeverity {
71 | IssueSeverity::High
72 | }
73 |
74 | fn title(&self) -> String {
75 | String::from("Parent Chain Demonstration")
76 | }
77 |
78 | fn description(&self) -> String {
79 | String::from("Parent Chain Demonstration")
80 | }
81 |
82 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
83 | self.found_instances.clone()
84 | }
85 |
86 | fn name(&self) -> String {
87 | format!("{}", IssueDetectorNamePool::CentralizationRisk)
88 | }
89 | }
90 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/solmate_safe_transfer_lib.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 SolmateSafeTransferLibDetector {
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 SolmateSafeTransferLibDetector {
19 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
20 | for import_directive in context.import_directives() {
21 | // If the import directive absolute_path contains the strings "solmate" and
22 | // "SafeTransferLib", flip the found_solmate_import flag to true
23 | if import_directive.absolute_path.as_ref().unwrap().contains("solmate")
24 | && import_directive.absolute_path.as_ref().unwrap().contains("SafeTransferLib")
25 | {
26 | capture!(self, context, import_directive);
27 | }
28 | }
29 |
30 | Ok(!self.found_instances.is_empty())
31 | }
32 |
33 | fn severity(&self) -> IssueSeverity {
34 | IssueSeverity::Low
35 | }
36 |
37 | fn title(&self) -> String {
38 | String::from("Solmate\'s SafeTransferLib")
39 | }
40 |
41 | fn description(&self) -> String {
42 | String::from(
43 | "There is a subtle difference between the implementation of solmate's SafeTransferLib and OZ's SafeERC20: OZ's SafeERC20 checks if the token is a contract or not, solmate's SafeTransferLib does not.\nhttps://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol#L9 \n`@dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller`\n",
44 | )
45 | }
46 |
47 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
48 | self.found_instances.clone()
49 | }
50 |
51 | fn name(&self) -> String {
52 | format!("{}", IssueDetectorNamePool::SolmateSafeTransferLib)
53 | }
54 | }
55 |
56 | #[cfg(test)]
57 | mod solmate_safe_transfer_lib_tests {
58 |
59 | use crate::detect::{detector::IssueDetector, low::SolmateSafeTransferLibDetector};
60 |
61 | #[test]
62 |
63 | fn test_solmate_safe_transfer_lib_by_loading_contract_directly() {
64 | let context = crate::detect::test_utils::load_solidity_source_unit(
65 | "../tests/contract-playground/src/T11sTranferer.sol",
66 | );
67 |
68 | let mut detector = SolmateSafeTransferLibDetector::default();
69 | let found = detector.detect(&context).unwrap();
70 | assert!(found);
71 | assert_eq!(detector.instances().len(), 1);
72 | }
73 |
74 | #[test]
75 |
76 | fn test_solmate_safe_transfer_lib_no_issue_by_loading_contract_directly() {
77 | let context = crate::detect::test_utils::load_solidity_source_unit(
78 | "../tests/contract-playground/src/ArbitraryTransferFrom.sol",
79 | );
80 |
81 | let mut detector = SolmateSafeTransferLibDetector::default();
82 | let found = detector.detect(&context).unwrap();
83 | assert!(!found);
84 | assert_eq!(detector.instances().len(), 0);
85 | }
86 | }
87 |
```
--------------------------------------------------------------------------------
/tests/ast/two_base_functions.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"A":[5],"B":[10],"C":[22]},"id":23,"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":"45:2:1","statements":[]},"functionSelector":"26121ff0","id":4,"implemented":true,"kind":"function","modifiers":[],"name":"f","nameLocation":"26:1:1","nodeType":"FunctionDefinition","parameters":{"id":1,"nodeType":"ParameterList","parameters":[],"src":"27:2:1"},"returnParameters":{"id":2,"nodeType":"ParameterList","parameters":[],"src":"45:0:1"},"scope":5,"src":"17:30:1","stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"scope":23,"src":"0:49:1","usedErrors":[]},{"abstract":false,"baseContracts":[],"canonicalName":"B","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":10,"linearizedBaseContracts":[10],"name":"B","nameLocation":"59:1:1","nodeType":"ContractDefinition","nodes":[{"body":{"id":8,"nodeType":"Block","src":"95:2:1","statements":[]},"functionSelector":"26121ff0","id":9,"implemented":true,"kind":"function","modifiers":[],"name":"f","nameLocation":"76:1:1","nodeType":"FunctionDefinition","parameters":{"id":6,"nodeType":"ParameterList","parameters":[],"src":"77:2:1"},"returnParameters":{"id":7,"nodeType":"ParameterList","parameters":[],"src":"95:0:1"},"scope":10,"src":"67:30:1","stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"scope":23,"src":"50:49:1","usedErrors":[]},{"abstract":false,"baseContracts":[{"baseName":{"id":11,"name":"A","nameLocations":["114:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":5,"src":"114:1:1"},"id":12,"nodeType":"InheritanceSpecifier","src":"114:1:1"},{"baseName":{"id":13,"name":"B","nameLocations":["117:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":10,"src":"117:1:1"},"id":14,"nodeType":"InheritanceSpecifier","src":"117:1:1"}],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":22,"linearizedBaseContracts":[22,10,5],"name":"C","nameLocation":"109:1:1","nodeType":"ContractDefinition","nodes":[{"baseFunctions":[4,9],"body":{"id":20,"nodeType":"Block","src":"160:2:1","statements":[]},"functionSelector":"26121ff0","id":21,"implemented":true,"kind":"function","modifiers":[],"name":"f","nameLocation":"134:1:1","nodeType":"FunctionDefinition","overrides":{"id":18,"nodeType":"OverrideSpecifier","overrides":[{"id":16,"name":"A","nameLocations":["154:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":5,"src":"154:1:1"},{"id":17,"name":"B","nameLocations":["157:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":10,"src":"157:1:1"}],"src":"145:14:1"},"parameters":{"id":15,"nodeType":"ParameterList","parameters":[],"src":"135:2:1"},"returnParameters":{"id":19,"nodeType":"ParameterList","parameters":[],"src":"160:0:1"},"scope":22,"src":"125:37:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":23,"src":"100:64:1","usedErrors":[]}],"src":"0:165:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/stats/cloc.rs:
--------------------------------------------------------------------------------
```rust
1 | use super::token::*;
2 |
3 | pub fn count_code_lines(token_descriptors: &[TokenDescriptor]) -> usize {
4 | let mut code_lines = 0;
5 |
6 | let insights =
7 | token_descriptors.iter().map(|tok_dsc| tok_dsc.into()).collect::<Vec<TokenInsight>>();
8 |
9 | let mut token_insight_groups = vec![];
10 |
11 | for insight in &insights {
12 | if token_insight_groups.is_empty() {
13 | let new_token_insight_group = TokenInsightGroup {
14 | token_insights: vec![insight.clone()],
15 | start_line: insight.start_line,
16 | end_line: insight.end_line,
17 | token_type: insight.token_type.clone().into(),
18 | };
19 | token_insight_groups.push(new_token_insight_group);
20 | continue;
21 | }
22 | let prev_group = token_insight_groups.last_mut().unwrap();
23 |
24 | if insight.start_line == prev_group.end_line
25 | && insight.code_lines.actual_first_line == 0
26 | && prev_group.last_token_insight_has_code_in_its_last_line()
27 | && prev_group.token_type == insight.token_type.clone().into()
28 | {
29 | prev_group.token_insights.push(insight.clone());
30 | prev_group.end_line = insight.end_line;
31 | } else {
32 | let new_token_insight_group = TokenInsightGroup {
33 | token_insights: vec![insight.clone()],
34 | start_line: insight.start_line,
35 | end_line: insight.end_line,
36 | token_type: insight.token_type.clone().into(),
37 | };
38 | token_insight_groups.push(new_token_insight_group);
39 | }
40 | }
41 |
42 | let groups = token_insight_groups
43 | .iter()
44 | .filter(|g| g.token_type == HighLevelType::Code)
45 | .collect::<Vec<_>>();
46 |
47 | if groups.is_empty() {
48 | return 0;
49 | }
50 |
51 | let len = groups.len();
52 | let mut prev = &groups[0];
53 |
54 | code_lines += prev.total_contribution();
55 |
56 | #[allow(clippy::needless_range_loop)]
57 | for i in 1..len {
58 | let curr = &groups[i];
59 | let grp_contrib = curr.total_contribution();
60 | code_lines += grp_contrib;
61 |
62 | // what line does the first contributing token start ?
63 | if curr.start_line == prev.end_line
64 | && (curr.token_insights[0].code_lines.actual_first_line == 0)
65 | && grp_contrib >= 1
66 | && prev.last_token_insight_has_code_in_its_last_line()
67 | {
68 | // println!("deducting {} {}", curr.start_line, prev.end_line);
69 | code_lines -= 1;
70 | }
71 | prev = curr;
72 | }
73 |
74 | code_lines
75 | }
76 |
77 | #[cfg(test)]
78 | mod cloc_tests {
79 | use crate::stats::token::tokenize;
80 |
81 | use super::*;
82 |
83 | #[test]
84 | fn test_print_cloc_heavily_commented_contract() {
85 | let content = tokenize(include_str!(
86 | "../../../tests/contract-playground/src/cloc/HeavilyCommentedContract.sol"
87 | ));
88 | let stats = count_code_lines(&content);
89 | assert_eq!(stats, 21);
90 | }
91 |
92 | #[test]
93 | fn test_print_cloc_another_heavily_commented_contract() {
94 | let content = tokenize(include_str!(
95 | "../../../tests/contract-playground/src/cloc/AnotherHeavilyCommentedContract.sol"
96 | ));
97 | let stats = count_code_lines(&content);
98 | assert_eq!(stats, 32);
99 | }
100 | }
101 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/ast/impls/node/functions.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{ast::*, visitor::ast_visitor::*};
2 | use eyre::Result;
3 |
4 | impl Node for ParameterList {
5 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
6 | if visitor.visit_parameter_list(self)? {
7 | list_accept(&self.parameters, visitor)?;
8 | }
9 | self.accept_metadata(visitor)?;
10 | visitor.end_visit_parameter_list(self)
11 | }
12 | fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
13 | let parameters_ids = &self.parameters.iter().map(|x| x.id).collect::<Vec<_>>();
14 | visitor.visit_immediate_children(self.id, parameters_ids.clone())?;
15 | Ok(())
16 | }
17 | macros::accept_id!();
18 | }
19 |
20 | impl Node for OverrideSpecifier {
21 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
22 | if visitor.visit_override_specifier(self)? {
23 | for overrider in &self.overrides {
24 | match overrider {
25 | UserDefinedTypeNameOrIdentifierPath::UserDefinedTypeName(type_name) => {
26 | type_name.accept(visitor)?
27 | }
28 | UserDefinedTypeNameOrIdentifierPath::IdentifierPath(identifier_path) => {
29 | identifier_path.accept(visitor)?
30 | }
31 | }
32 | }
33 | }
34 | self.accept_metadata(visitor)?;
35 | visitor.end_visit_override_specifier(self)
36 | }
37 | fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
38 | let overrides_ids =
39 | &self.overrides.iter().filter_map(|x| x.get_node_id()).collect::<Vec<_>>();
40 | visitor.visit_immediate_children(self.id, overrides_ids.clone())?;
41 | Ok(())
42 | }
43 | macros::accept_id!();
44 | }
45 |
46 | impl Node for FunctionDefinition {
47 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
48 | if visitor.visit_function_definition(self)? {
49 | if self.documentation.is_some() {
50 | self.documentation.as_ref().unwrap().accept(visitor)?;
51 | }
52 | if self.overrides.is_some() {
53 | self.overrides.as_ref().unwrap().accept(visitor)?;
54 | }
55 | self.parameters.accept(visitor)?;
56 | self.return_parameters.accept(visitor)?;
57 | list_accept(&self.modifiers, visitor)?;
58 | if self.body.is_some() {
59 | self.body.as_ref().unwrap().accept(visitor)?;
60 | }
61 | }
62 | self.accept_metadata(visitor)?;
63 | visitor.end_visit_function_definition(self)
64 | }
65 | fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
66 | // TODO: documentation
67 | if let Some(overrides) = &self.overrides {
68 | visitor.visit_immediate_children(self.id, vec![overrides.id])?;
69 | }
70 | visitor.visit_immediate_children(self.id, vec![self.parameters.id])?;
71 | visitor.visit_immediate_children(self.id, vec![self.return_parameters.id])?;
72 | let modifiers_ids = &self.modifiers.iter().map(|x| x.id).collect::<Vec<_>>();
73 | visitor.visit_immediate_children(self.id, modifiers_ids.clone())?;
74 | if let Some(body) = &self.body {
75 | visitor.visit_immediate_children(self.id, vec![body.id])?;
76 | }
77 | Ok(())
78 | }
79 | macros::accept_id!();
80 | }
81 |
```
--------------------------------------------------------------------------------
/aderyn_driver/src/display.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{
2 | collections::{BTreeMap, HashSet},
3 | path::PathBuf,
4 | };
5 |
6 | use solidity_ast::{AstSourceFile, ProjectConfigInput};
7 |
8 | pub fn display_header(project_config: &ProjectConfigInput, header: &str) {
9 | let say_header = |message: &str| {
10 | let say = |message: &str| {
11 | println!("{}", message);
12 | };
13 | let longest_str_len = project_config.project_paths.sources.display().to_string().len();
14 | say(&format!("---------{}", &"-".repeat(longest_str_len)));
15 | say(&format!("# {}", message));
16 | say(&format!("---------{}", &"-".repeat(longest_str_len)));
17 | };
18 | say_header(header);
19 | }
20 |
21 | pub fn display_ingesting_message(
22 | sources_ast: &BTreeMap<PathBuf, AstSourceFile>,
23 | included: &HashSet<PathBuf>,
24 | solc_version: &str,
25 | ) {
26 | let ingestion_keys = sources_ast.keys().filter(|&key| included.contains(key)).count();
27 |
28 | if ingestion_keys > 0 {
29 | println!("Ingesting {} compiled files [solc : v{}]", ingestion_keys, solc_version);
30 | } else {
31 | eprintln!("No files found for context [solc : v{}]", solc_version);
32 | }
33 | }
34 |
35 | pub fn display_configuration_info(project_config: &ProjectConfigInput) {
36 | let say = |message: &str| {
37 | println!("{}", message);
38 | };
39 |
40 | let say_header = |message: &str| {
41 | let longest_str_len = project_config.project_paths.sources.display().to_string().len();
42 | say(&format!("---------{}", &"-".repeat(longest_str_len)));
43 | say(&format!("# {}", message));
44 | say(&format!("---------{}", &"-".repeat(longest_str_len)));
45 | };
46 |
47 | say("");
48 | say_header("Configuration");
49 | say(&format!("Root - {}", project_config.project_paths.root.display()));
50 | say(&format!("Source - {}", project_config.project_paths.sources.display()));
51 | say(&format!(
52 | "Remappings - {:#?}",
53 | project_config
54 | .project_paths
55 | .remappings
56 | .iter()
57 | .map(|r| {
58 | let mut rel = r.clone();
59 | rel.strip_prefix(&project_config.project_paths.root);
60 | rel.to_string()
61 | })
62 | .collect::<Vec<_>>()
63 | ));
64 | say(&format!("EVM version - {}", project_config.evm_version));
65 |
66 | say_header("Source Scope");
67 | if project_config.include_containing.clone() != vec!["".to_string()] {
68 | say(&format!("Include Filepaths Containing - {:#?}", project_config.include_containing));
69 | } else {
70 | say("Include Filepaths Containing - No specific criteria.");
71 | }
72 | if !project_config.exclude_containing.is_empty() {
73 | say(&format!("Exclude Filepaths Containing - {:#?}", project_config.exclude_containing));
74 | } else {
75 | say("Exclude Filepaths Containing - No specific criteria.");
76 | }
77 |
78 | if !project_config.exclude_starting.is_empty() {
79 | say(&format!(
80 | "Auto Excluding Files - {:#?}",
81 | project_config
82 | .exclude_starting
83 | .iter()
84 | .map(|r| {
85 | r.strip_prefix(&project_config.project_paths.sources)
86 | .unwrap_or(r)
87 | .to_string_lossy()
88 | .to_string()
89 | })
90 | .collect::<Vec<_>>()
91 | ));
92 | } else {
93 | say("Auto Excluding - No Files.");
94 | }
95 | }
96 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/browser/external_calls.rs:
--------------------------------------------------------------------------------
```rust
1 | //! This module helps us detect whether a given AST Node has any external calls inside of it
2 |
3 | use super::ExtractMemberAccesses;
4 | use crate::context::workspace::ASTNode;
5 |
6 | pub fn is_extcallish(ast_node: ASTNode) -> bool {
7 | // This is so we can skip the FunctionCallOptions layer which solidity compiler inserts
8 | // when there are options passed to function calls
9 | for member_access in ExtractMemberAccesses::from(&ast_node).extracted {
10 | // address(..).call("...") pattern
11 | let is_call = member_access.member_name == "call";
12 | if is_call {
13 | return true;
14 | }
15 |
16 | // payable(address(..)).transfer(100)
17 | // payable(address(..)).send(100)
18 | // address.sendValue(..) (from openzeppelin)
19 | if (member_access.member_name == "transfer"
20 | || member_access.member_name == "send"
21 | || member_access.member_name == "sendValue")
22 | && let Some(type_description) = member_access.expression.type_descriptions()
23 | && type_description
24 | .type_string
25 | .as_ref()
26 | .is_some_and(|type_string| type_string.starts_with("address"))
27 | {
28 | return true;
29 | }
30 |
31 | // Any external call
32 | if member_access
33 | .type_descriptions
34 | .type_identifier
35 | .is_some_and(|type_identifier| type_identifier.contains("function_external"))
36 | {
37 | return true;
38 | }
39 | }
40 |
41 | false
42 | }
43 |
44 | #[cfg(test)]
45 | mod external_calls_detector {
46 |
47 | use crate::{
48 | ast::*, context::browser::ExtractFunctionCalls,
49 | detect::test_utils::load_solidity_source_unit,
50 | };
51 |
52 | impl FunctionDefinition {
53 | pub fn makes_external_calls(&self) -> bool {
54 | let func_calls = ExtractFunctionCalls::from(self).extracted;
55 | func_calls.iter().any(|f| f.is_extcallish())
56 | }
57 | }
58 |
59 | #[test]
60 |
61 | fn test_direct_call_on_address() {
62 | let context =
63 | load_solidity_source_unit("../tests/contract-playground/src/ExternalCalls.sol");
64 |
65 | let childex = context.find_contract_by_name("ChildEx");
66 |
67 | let ext1 = childex.find_function_by_name("ext1");
68 | let ext2 = childex.find_function_by_name("ext2");
69 | let ext3 = childex.find_function_by_name("ext3");
70 | let ext4 = childex.find_function_by_name("ext4");
71 | let ext5 = childex.find_function_by_name("ext5");
72 | let ext6 = childex.find_function_by_name("ext6");
73 | let ext7 = childex.find_function_by_name("ext7");
74 | let ext8 = childex.find_function_by_name("ext8");
75 | let ext9 = childex.find_function_by_name("ext9");
76 |
77 | assert!(ext1.makes_external_calls());
78 | assert!(ext2.makes_external_calls());
79 | assert!(ext3.makes_external_calls());
80 | assert!(ext4.makes_external_calls());
81 | assert!(ext5.makes_external_calls());
82 | assert!(ext6.makes_external_calls());
83 | assert!(ext7.makes_external_calls());
84 | assert!(ext8.makes_external_calls());
85 | assert!(ext9.makes_external_calls());
86 |
87 | let notext1 = childex.find_function_by_name("notExt1");
88 | let notext2 = childex.find_function_by_name("notExt2");
89 |
90 | assert!(!notext1.makes_external_calls());
91 | assert!(!notext2.makes_external_calls());
92 | }
93 | }
94 |
```
--------------------------------------------------------------------------------
/tests/ast/non_utf8.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"C":[15]},"id":16,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":15,"linearizedBaseContracts":[15],"name":"C","nameLocation":"9:1:1","nodeType":"ContractDefinition","nodes":[{"body":{"id":13,"nodeType":"Block","src":"33:45:1","statements":[{"assignments":[4],"declarations":[{"constant":false,"id":4,"mutability":"mutable","name":"x","nameLocation":"49:1:1","nodeType":"VariableDeclaration","scope":13,"src":"35:15:1","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":3,"name":"string","nodeType":"ElementaryTypeName","src":"35:6:1","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"id":12,"initialValue":{"arguments":[{"arguments":[{"hexValue":"ff","id":9,"isConstant":false,"isLValue":false,"isPure":true,"kind":"hexString","lValueRequested":false,"nodeType":"Literal","src":"66:7:1","typeDescriptions":{"typeIdentifier":"t_stringliteral_8b1a944cf13a9a1c08facb2c9e98623ef3254d2ddb48113885c3e8e97fec8db9","typeString":"literal_string hex\"ff\""}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_8b1a944cf13a9a1c08facb2c9e98623ef3254d2ddb48113885c3e8e97fec8db9","typeString":"literal_string hex\"ff\""}],"id":8,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"60:5:1","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes_storage_ptr_$","typeString":"type(bytes storage pointer)"},"typeName":{"id":7,"name":"bytes","nodeType":"ElementaryTypeName","src":"60:5:1","typeDescriptions":{}}},"id":10,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"60:14:1","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"id":6,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"53:6:1","typeDescriptions":{"typeIdentifier":"t_type$_t_string_storage_ptr_$","typeString":"type(string storage pointer)"},"typeName":{"id":5,"name":"string","nodeType":"ElementaryTypeName","src":"53:6:1","typeDescriptions":{}}},"id":11,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"53:22:1","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},"nodeType":"VariableDeclarationStatement","src":"35:40:1"}]},"functionSelector":"26121ff0","id":14,"implemented":true,"kind":"function","modifiers":[],"name":"f","nameLocation":"22:1:1","nodeType":"FunctionDefinition","parameters":{"id":1,"nodeType":"ParameterList","parameters":[],"src":"23:2:1"},"returnParameters":{"id":2,"nodeType":"ParameterList","parameters":[],"src":"33:0:1"},"scope":15,"src":"13:65:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":16,"src":"0:80:1","usedErrors":[]}],"src":"0:81:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unspecific_solidity_pragma.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::{ContractKind, NodeID, NodeType},
5 | capture,
6 | context::{
7 | browser::{ExtractContractDefinitions, GetClosestAncestorOfTypeX},
8 | workspace::WorkspaceContext,
9 | },
10 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
11 | };
12 | use eyre::Result;
13 |
14 | #[derive(Default)]
15 | pub struct UnspecificSolidityPragmaDetector {
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 UnspecificSolidityPragmaDetector {
22 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
23 | for pragma_directive in context.pragma_directives() {
24 | let Some(source_unit) =
25 | pragma_directive.closest_ancestor_of_type(context, NodeType::SourceUnit)
26 | else {
27 | continue;
28 | };
29 | let contracts_in_source_unit = ExtractContractDefinitions::from(source_unit).extracted;
30 | if contracts_in_source_unit.iter().any(|c| c.kind == ContractKind::Library) {
31 | continue;
32 | }
33 | for literal in &pragma_directive.literals {
34 | if literal.contains('^') || literal.contains('>') || literal.contains('<') {
35 | capture!(self, context, pragma_directive);
36 | break;
37 | }
38 | }
39 | }
40 | Ok(!self.found_instances.is_empty())
41 | }
42 |
43 | fn title(&self) -> String {
44 | String::from("Unspecific Solidity Pragma")
45 | }
46 |
47 | fn description(&self) -> String {
48 | String::from(
49 | "Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;`",
50 | )
51 | }
52 |
53 | fn severity(&self) -> IssueSeverity {
54 | IssueSeverity::Low
55 | }
56 |
57 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
58 | self.found_instances.clone()
59 | }
60 |
61 | fn name(&self) -> String {
62 | format!("{}", IssueDetectorNamePool::UnspecificSolidityPragma)
63 | }
64 | }
65 |
66 | #[cfg(test)]
67 | mod unspecific_solidity_pragma_tests {
68 | use crate::detect::{
69 | detector::IssueDetector, low::unspecific_solidity_pragma::UnspecificSolidityPragmaDetector,
70 | };
71 |
72 | #[test]
73 |
74 | fn test_unspecific_solidity_pragma_detector_by_loading_contract_directly() {
75 | let context = crate::detect::test_utils::load_solidity_source_unit(
76 | "../tests/contract-playground/src/inheritance/IContractInheritance.sol",
77 | );
78 |
79 | let mut detector = UnspecificSolidityPragmaDetector::default();
80 | let found = detector.detect(&context).unwrap();
81 | assert!(found);
82 | // failure0, failure1 and failure3
83 | assert_eq!(detector.instances().len(), 1);
84 | }
85 |
86 | #[test]
87 |
88 | fn test_unspecific_solidity_pragma_detector_by_loading_contract_directly_on_library() {
89 | let context = crate::detect::test_utils::load_solidity_source_unit(
90 | "../tests/contract-playground/src/OnlyLibrary.sol",
91 | );
92 |
93 | let mut detector = UnspecificSolidityPragmaDetector::default();
94 | let found = detector.detect(&context).unwrap();
95 | assert!(!found);
96 | }
97 | }
98 |
```
--------------------------------------------------------------------------------
/tests/ast/function_type.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"C":[17]},"id":18,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":17,"linearizedBaseContracts":[17],"name":"C","nameLocation":"9:1:1","nodeType":"ContractDefinition","nodes":[{"body":{"id":15,"nodeType":"Block","src":"127:2:1","statements":[]},"functionSelector":"d6cd4974","id":16,"implemented":true,"kind":"function","modifiers":[],"name":"f","nameLocation":"22:1:1","nodeType":"FunctionDefinition","parameters":{"id":7,"nodeType":"ParameterList","parameters":[{"constant":false,"id":6,"mutability":"mutable","name":"x","nameLocation":"67:1:1","nodeType":"VariableDeclaration","scope":16,"src":"24:44:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_external_payable$__$returns$_t_uint256_$","typeString":"function () payable external returns (uint256)"},"typeName":{"id":5,"nodeType":"FunctionTypeName","parameterTypes":{"id":1,"nodeType":"ParameterList","parameters":[],"src":"32:2:1"},"returnParameterTypes":{"id":4,"nodeType":"ParameterList","parameters":[{"constant":false,"id":3,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":5,"src":"61:4:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":2,"name":"uint","nodeType":"ElementaryTypeName","src":"61:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"60:6:1"},"src":"24:44:1","stateMutability":"payable","typeDescriptions":{"typeIdentifier":"t_function_external_payable$__$returns$_t_uint256_$","typeString":"function () payable external returns (uint256)"},"visibility":"external"},"visibility":"internal"}],"src":"23:46:1"},"returnParameters":{"id":14,"nodeType":"ParameterList","parameters":[{"constant":false,"id":13,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":16,"src":"86:40:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"},"typeName":{"id":12,"nodeType":"FunctionTypeName","parameterTypes":{"id":8,"nodeType":"ParameterList","parameters":[],"src":"94:2:1"},"returnParameterTypes":{"id":11,"nodeType":"ParameterList","parameters":[{"constant":false,"id":10,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":12,"src":"120:4:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":9,"name":"uint","nodeType":"ElementaryTypeName","src":"120:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"119:6:1"},"src":"86:40:1","stateMutability":"view","typeDescriptions":{"typeIdentifier":"t_function_external_view$__$returns$_t_uint256_$","typeString":"function () view external returns (uint256)"},"visibility":"external"},"visibility":"internal"}],"src":"85:41:1"},"scope":17,"src":"13:116:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":18,"src":"0:131:1","usedErrors":[]}],"src":"0:132:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/mcp/node_summarizer/tool.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{
2 | ast::NodeID,
3 | context::{
4 | macros::{mcp_error, mcp_success},
5 | mcp::{
6 | MCPToolNamePool, ModelContextProtocolState, ModelContextProtocolTool,
7 | node_summarizer::{render, utils::*},
8 | },
9 | },
10 | };
11 | use indoc::indoc;
12 | use rmcp::{
13 | ErrorData as McpError, handler::server::wrapper::Parameters, model::CallToolResult, schemars,
14 | };
15 | use serde::Deserialize;
16 | use std::sync::Arc;
17 |
18 | #[derive(Clone)]
19 | pub struct NodeSummarizerTool {
20 | state: Arc<ModelContextProtocolState>,
21 | }
22 |
23 | #[derive(Deserialize, schemars::JsonSchema)]
24 | pub struct NodeSummarizerPayload {
25 | /// The index of the compilation unit to analyze. Must be a positive integer starting from 1.
26 | /// Use the project overview tool first to see all available compilation units and their
27 | /// indices.
28 | pub compilation_unit_index: usize,
29 | /// The Node ID for which you want to see the code snippet and some basic summary about it,
30 | /// such as parent contract ID, etc.
31 | pub node_id: NodeID,
32 | }
33 |
34 | impl ModelContextProtocolTool for NodeSummarizerTool {
35 | type Input = NodeSummarizerPayload;
36 |
37 | fn new(state: Arc<ModelContextProtocolState>) -> Self {
38 | Self { state }
39 | }
40 |
41 | fn name(&self) -> String {
42 | MCPToolNamePool::AderynNodeSummarizer.to_string()
43 | }
44 |
45 | fn description(&self) -> String {
46 | indoc! {
47 | "Given a compilation unit index and a Node ID, returns a focused summary of that exact AST node\
48 | (e.g. function, modifier, event, variable, struct) and the source code snippet. Also in metadata show \
49 | the callgraphs that collide with the node if it is a function."
50 | }
51 | .to_string()
52 | }
53 |
54 | fn execute(&self, input: Parameters<Self::Input>) -> Result<CallToolResult, McpError> {
55 | let payload = input.0;
56 |
57 | if payload.compilation_unit_index < 1
58 | || payload.compilation_unit_index > self.state.contexts.len()
59 | {
60 | return mcp_error!(
61 | "Invalid compilation unit index: {}. Must be in range [1, {}]",
62 | payload.compilation_unit_index,
63 | self.state.contexts.len()
64 | );
65 | }
66 |
67 | let context = self
68 | .state
69 | .contexts
70 | .get(payload.compilation_unit_index - 1)
71 | .expect("Compilation unit index bounds check failed");
72 |
73 | let Some(node) = context.nodes.get(&payload.node_id) else {
74 | return mcp_error!(
75 | "Node with ID {} not found in compilation unit index {}",
76 | payload.node_id,
77 | payload.compilation_unit_index
78 | );
79 | };
80 |
81 | let (filepath, _, _) = context.get_node_sort_key_pure(node);
82 | let code_snippet = context.get_code_snippet(node);
83 |
84 | let summary = render::NodeSummaryBuilder::default()
85 | .compilation_unit_index(payload.compilation_unit_index)
86 | .node_id(payload.node_id)
87 | .filepath(filepath)
88 | .code(code_snippet)
89 | .containing_contract(get_containing_contract(context, node))
90 | .containing_function(get_containing_function(context, node))
91 | .containing_modifier(get_containing_modifier(context, node))
92 | .containing_callgraphs(get_containing_callgraphs(context, node))
93 | .build()
94 | .expect("failed to build node summary");
95 |
96 | mcp_success!(summary)
97 | }
98 | }
99 |
```
--------------------------------------------------------------------------------
/aderyn_driver/tests/astgen.rs:
--------------------------------------------------------------------------------
```rust
1 | #[cfg(test)]
2 | mod project_compiler_grouping_tests {
3 | use std::{env::set_var, path::PathBuf, str::FromStr};
4 |
5 | use aderyn_driver::{compile, process::PreprocessedConfig};
6 |
7 | // Tester function
8 | fn test_grouping_files_to_compile(
9 | project_root_str: &str,
10 | src: &Option<PathBuf>,
11 | include: &Option<Vec<String>>,
12 | exclude: &Option<Vec<String>>,
13 | ) {
14 | let root_path = PathBuf::from_str(project_root_str).unwrap();
15 | let source = if src.is_some() {
16 | Some(src.clone().unwrap().to_string_lossy().to_string())
17 | } else {
18 | None
19 | };
20 |
21 | let preprocessed_config = PreprocessedConfig {
22 | root_path,
23 | src: source,
24 | include: include.clone(),
25 | exclude: exclude.clone(),
26 | };
27 | let (contexts, _) = compile::compile_project(preprocessed_config, false, true).unwrap();
28 |
29 | assert!(!contexts.is_empty());
30 | contexts.iter().for_each(|c| {
31 | assert!(!c.source_units().is_empty());
32 | });
33 | }
34 |
35 | #[test]
36 | fn foundry_nft_f23_only() {
37 | let project_root_str = "../tests/foundry-nft-f23";
38 | let src = &Some(PathBuf::from_str("src/").unwrap());
39 | test_grouping_files_to_compile(project_root_str, src, &None, &None);
40 | }
41 |
42 | #[test]
43 | fn foundry_nft_f23_icm() {
44 | let project_root_str = "../tests/foundry-nft-f23-icm";
45 | unsafe { set_var("FOUNDRY_PROFILE", "icm") };
46 | test_grouping_files_to_compile(project_root_str, &None, &None, &None);
47 | }
48 |
49 | #[test]
50 | fn adhoc_solidity_files() {
51 | let project_root_str = "../tests/adhoc-sol-files";
52 | test_grouping_files_to_compile(project_root_str, &None, &None, &None);
53 | }
54 |
55 | #[test]
56 | fn contract_playground() {
57 | let project_root_str = "../tests/contract-playground";
58 | let src = &Some(PathBuf::from_str("src/").unwrap());
59 | test_grouping_files_to_compile(project_root_str, src, &None, &None);
60 | }
61 |
62 | // INFO: This CCIP unit test takes too much time to run. Since we already have
63 | // an integration test, let's not have this.
64 | //
65 | //#[test]
66 | //fn ccip_develop() {
67 | // let project_root_str = "../tests/ccip-contracts/contracts";
68 | // set_var("FOUNDRY_PROFILE", "vrfv2plus_coordinator");
69 | // test_grouping_files_to_compile(project_root_str, &None, &None, &None);
70 | //}
71 |
72 | #[test]
73 | fn test_no_files_found_in_scope_id_detected_by_context_src_filepaths() {
74 | let preprocessed_config = PreprocessedConfig {
75 | root_path: PathBuf::from("../tests/contract-playground").canonicalize().unwrap(),
76 | src: None,
77 | include: Some(vec!["NonExistentFile.sol".to_string()]),
78 | exclude: None,
79 | };
80 | let (contexts, _) = compile::compile_project(preprocessed_config, false, true).unwrap();
81 | assert!(contexts.iter().all(|c| c.src_filepaths.is_empty()));
82 | }
83 |
84 | #[test]
85 | fn test_compiler_input_returns_empty_vector_when_no_solidity_files_present() {
86 | let preprocessed_config = PreprocessedConfig {
87 | root_path: PathBuf::from_str("../tests/no-sol-files").unwrap(),
88 | src: None,
89 | include: None,
90 | exclude: Some(vec!["NonExistentFile.sol".to_string()]),
91 | };
92 | let (contexts, _) = compile::compile_project(preprocessed_config, false, true).unwrap();
93 | assert!(contexts.is_empty());
94 | }
95 | }
96 |
```
--------------------------------------------------------------------------------
/aderyn_driver/src/driver.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{
2 | interface::lsp::LspReport,
3 | mcp::McpServer,
4 | process::make_context,
5 | runner::{run_auditor_mode, run_detector_mode, run_lsp_mode},
6 | };
7 | use aderyn_core::detect::detector::{IssueDetector, IssueSeverity, get_all_issue_detectors};
8 | use field_access::FieldAccess;
9 | use std::sync::Arc;
10 | use tokio::sync::Mutex;
11 |
12 | #[derive(Clone, FieldAccess)]
13 | pub struct Args {
14 | pub input_config: CliArgsInputConfig,
15 | pub output_config: CliArgsOutputConfig,
16 | pub common_config: CliArgsCommonConfig,
17 | }
18 |
19 | #[derive(Debug, Clone)]
20 | pub struct CliArgsInputConfig {
21 | pub root: String,
22 | pub src: Option<String>,
23 | pub path_excludes: Option<Vec<String>>,
24 | pub path_includes: Option<Vec<String>>,
25 | }
26 |
27 | #[derive(Debug, Clone)]
28 | pub struct CliArgsOutputConfig {
29 | pub output: String,
30 | pub stdout: bool,
31 | pub no_snippets: bool,
32 | }
33 |
34 | #[derive(Debug, Clone)]
35 | pub struct CliArgsCommonConfig {
36 | pub verbose: bool,
37 | pub lsp: bool,
38 | pub skip_cloc: bool,
39 | pub highs_only: bool,
40 | }
41 |
42 | /// One way pipeline to print details for auditors. (for CLI)
43 | pub fn kick_off_audit_mode(args: Args) {
44 | let run_pipeline = || -> Result<(), Box<dyn std::error::Error>> {
45 | let cx_wrapper =
46 | make_context(&args.input_config, &args.common_config).unwrap_or_else(|e| {
47 | eprintln!("Error making context: {}", e);
48 | std::process::exit(1);
49 | });
50 |
51 | run_auditor_mode(&cx_wrapper.contexts)?;
52 | Ok(())
53 | };
54 |
55 | // Kick-off
56 | run_pipeline().unwrap_or_else(|e| {
57 | eprintln!("Error driving aderyn: {}", e);
58 | std::process::exit(1);
59 | });
60 | }
61 |
62 | /// One way pipeline to generate vulnerability reports. (for CLI)
63 | pub fn kick_off_report_creation(args: Args) {
64 | let detectors = detector_list(&args);
65 |
66 | let run_pipeline = || -> Result<(), Box<dyn std::error::Error>> {
67 | let cx_wrapper =
68 | make_context(&args.input_config, &args.common_config).unwrap_or_else(|e| {
69 | eprintln!("Error making context: {}", e);
70 | std::process::exit(1);
71 | });
72 |
73 | // Load the workspace context into the run function, which runs the detectors
74 | run_detector_mode(&cx_wrapper, detectors, &args.output_config)?;
75 | Ok(())
76 | };
77 |
78 | // Kick-off
79 | run_pipeline().unwrap_or_else(|e| {
80 | eprintln!("Error driving aderyn: {}", e);
81 | std::process::exit(1);
82 | });
83 | }
84 |
85 | /// Identify and return vulnerability reports. (for LSP)
86 | pub fn fetch_report_for_lsp(args: Args) -> Arc<Mutex<Option<LspReport>>> {
87 | let detectors = detector_list(&args);
88 |
89 | let ctx_wrapper = match make_context(&args.input_config, &args.common_config) {
90 | Ok(ctx_wrapper) => ctx_wrapper,
91 | Err(_) => {
92 | return Arc::new(tokio::sync::Mutex::new(None));
93 | }
94 | };
95 |
96 | let lsp_report = run_lsp_mode(&ctx_wrapper, detectors);
97 |
98 | Arc::new(tokio::sync::Mutex::new(lsp_report))
99 | }
100 |
101 | /// Create MCP server with context fed
102 | pub fn create_mcp_server(args: Args) -> Option<McpServer> {
103 | let ctx_wrapper = match make_context(&args.input_config, &args.common_config) {
104 | Ok(ctx_wrapper) => ctx_wrapper,
105 | Err(_) => return None,
106 | };
107 | Some(McpServer::new(ctx_wrapper))
108 | }
109 |
110 | fn detector_list(args: &Args) -> Vec<Box<dyn IssueDetector>> {
111 | get_all_issue_detectors()
112 | .into_iter()
113 | .filter(|d| !args.common_config.highs_only || d.severity() == IssueSeverity::High)
114 | .collect()
115 | }
116 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unchecked_return.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{ASTNode, Expression, Identifier, MemberAccess, NodeID, NodeType};
4 |
5 | use crate::{
6 | capture,
7 | context::{browser::GetImmediateParent, workspace::WorkspaceContext},
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct UncheckedReturnDetector {
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 UncheckedReturnDetector {
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 function_call in context.function_calls() {
27 | // Find the ID of FunctionDefinition that we're calling so that we may identify if there
28 | // are returned params
29 | match function_call.expression.as_ref() {
30 | Expression::Identifier(Identifier { referenced_declaration: Some(id), .. })
31 | | Expression::MemberAccess(MemberAccess {
32 | referenced_declaration: Some(id), ..
33 | }) => {
34 | if let Some(ASTNode::ExpressionStatement(func_call_parent)) =
35 | function_call.parent(context)
36 | && func_call_parent
37 | .parent(context)
38 | .is_some_and(|node| node.node_type() == NodeType::Block)
39 | {
40 | // Now, we know that the return value is unused
41 | if let Some(ASTNode::FunctionDefinition(function)) = context.nodes.get(id)
42 | && !function.return_parameters.parameters.is_empty()
43 | {
44 | // Now, we know that the function has no return value
45 | capture!(self, context, function_call);
46 | }
47 | }
48 | }
49 | _ => (),
50 | };
51 | }
52 |
53 | Ok(!self.found_instances.is_empty())
54 | }
55 |
56 | fn severity(&self) -> IssueSeverity {
57 | IssueSeverity::Low
58 | }
59 |
60 | fn title(&self) -> String {
61 | String::from("Unchecked Return")
62 | }
63 |
64 | fn description(&self) -> String {
65 | String::from(
66 | "Function returns a value but it is ignored. Consider checking the return value.",
67 | )
68 | }
69 |
70 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
71 | self.found_instances.clone()
72 | }
73 |
74 | fn name(&self) -> String {
75 | IssueDetectorNamePool::UncheckedReturn.to_string()
76 | }
77 | }
78 |
79 | #[cfg(test)]
80 | mod unchecked_return_tests {
81 |
82 | use crate::detect::{detector::IssueDetector, low::unchecked_return::UncheckedReturnDetector};
83 |
84 | #[test]
85 |
86 | fn test_unchecked_return_detector() {
87 | let context = crate::detect::test_utils::load_solidity_source_unit(
88 | "../tests/contract-playground/src/UncheckedReturn.sol",
89 | );
90 |
91 | let mut detector = UncheckedReturnDetector::default();
92 | let found = detector.detect(&context).unwrap();
93 |
94 | assert!(found);
95 | assert_eq!(detector.instances().len(), 2);
96 | }
97 | }
98 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/delegatecall_in_loop.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::{ASTNode, NodeID},
5 | capture,
6 | context::{
7 | browser::ExtractMemberAccesses,
8 | graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
9 | workspace::WorkspaceContext,
10 | },
11 | detect::{
12 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
13 | helpers::get_explore_centers_of_loops,
14 | },
15 | };
16 | use eyre::Result;
17 |
18 | #[derive(Default)]
19 | pub struct DelegatecallInLoopDetector {
20 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
21 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
22 | found_instances: BTreeMap<(String, usize, String), NodeID>,
23 | }
24 |
25 | impl IssueDetector for DelegatecallInLoopDetector {
26 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
27 | // PLAN
28 | // Explore inward from loops and track all the `delegatecall` that you come across
29 |
30 | let loop_explore_centers = get_explore_centers_of_loops(context);
31 |
32 | for explore_center in loop_explore_centers {
33 | // TODO: capture hints!
34 |
35 | // All the ASTNodes that are potentially run in a loop
36 | let callgraphs =
37 | CallGraphConsumer::get(context, &[explore_center], CallGraphDirection::Inward)?;
38 |
39 | for callgraph in callgraphs {
40 | let mut delegate_call_tracker = DelegateCallTracker::default();
41 | callgraph.accept(context, &mut delegate_call_tracker)?;
42 |
43 | if delegate_call_tracker.has_delegate_call {
44 | capture!(self, context, explore_center);
45 | }
46 | }
47 | }
48 |
49 | Ok(!self.found_instances.is_empty())
50 | }
51 |
52 | fn severity(&self) -> IssueSeverity {
53 | IssueSeverity::Low
54 | }
55 |
56 | fn title(&self) -> String {
57 | String::from("`delegatecall` in loop")
58 | }
59 |
60 | fn description(&self) -> String {
61 | String::from(
62 | "Using `delegatecall` in loop may consume excessive gas, or worse, lead to more severe issues.",
63 | )
64 | }
65 |
66 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
67 | self.found_instances.clone()
68 | }
69 |
70 | fn name(&self) -> String {
71 | format!("{}", IssueDetectorNamePool::DelegatecallInLoop)
72 | }
73 | }
74 |
75 | #[derive(Default)]
76 | struct DelegateCallTracker {
77 | has_delegate_call: bool,
78 | }
79 |
80 | impl CallGraphVisitor for DelegateCallTracker {
81 | fn visit_any(&mut self, node: &ASTNode) -> eyre::Result<()> {
82 | if self.has_delegate_call {
83 | return Ok(());
84 | }
85 |
86 | let dcalls = ExtractMemberAccesses::from(node)
87 | .extracted
88 | .into_iter()
89 | .filter(|ma| ma.member_name == "delegatecall")
90 | .count();
91 |
92 | self.has_delegate_call = dcalls > 0;
93 |
94 | Ok(())
95 | }
96 | }
97 |
98 | #[cfg(test)]
99 | mod delegate_call_in_loop_detector_tests {
100 |
101 | use super::DelegatecallInLoopDetector;
102 | use crate::detect::detector::IssueDetector;
103 |
104 | #[test]
105 |
106 | fn test_delegate_call_in_loop_detector_by_loading_contract_directly() {
107 | let context = crate::detect::test_utils::load_solidity_source_unit(
108 | "../tests/contract-playground/src/inheritance/ExtendedInheritance.sol",
109 | );
110 |
111 | let mut detector = DelegatecallInLoopDetector::default();
112 | let found = detector.detect(&context).unwrap();
113 | assert!(found);
114 | assert_eq!(detector.instances().len(), 1);
115 | }
116 | }
117 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/empty_block.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::{FunctionKind, NodeID, NodeType},
5 | capture,
6 | context::{
7 | browser::{GetAncestralLine, GetClosestAncestorOfTypeX},
8 | workspace::{ASTNode, WorkspaceContext},
9 | },
10 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
11 | };
12 | use eyre::Result;
13 |
14 | #[derive(Default)]
15 | pub struct EmptyBlockDetector {
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 EmptyBlockDetector {
22 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
23 | for empty_block in context.blocks().iter().filter(|b| b.statements.is_empty()) {
24 | if let Some(ASTNode::FunctionDefinition(f)) =
25 | &empty_block.closest_ancestor_of_type(context, NodeType::FunctionDefinition)
26 | {
27 | // It's okay to have empty block if it's a constructor, receive, or fallback
28 | if *f.kind() == FunctionKind::Function {
29 | capture!(self, context, f);
30 | } else if *f.kind() == FunctionKind::Constructor
31 | || *f.kind() == FunctionKind::Receive
32 | || *f.kind() == FunctionKind::Fallback
33 | {
34 | // It's not okay to have empty block nested somewhere inside constructor
35 | if let Some(block_chain) = empty_block.ancestral_line(context) {
36 | let function_definition_index = block_chain
37 | .iter()
38 | .position(|x| x.node_type() == NodeType::FunctionDefinition)
39 | .unwrap(); // Remember, we know we are already inside a constructor Function
40 |
41 | //We start from going up from first parent to the Function definition
42 | if function_definition_index > 1 {
43 | // 1 here, means the first parent.
44 | // So if the constructor is NOT the immediate parent of this empty block
45 | // capture it!
46 | capture!(self, context, empty_block);
47 | }
48 | }
49 | }
50 | }
51 | }
52 | Ok(!self.found_instances.is_empty())
53 | }
54 |
55 | fn title(&self) -> String {
56 | String::from("Empty Block")
57 | }
58 |
59 | fn description(&self) -> String {
60 | String::from("Consider removing empty blocks.")
61 | }
62 |
63 | fn severity(&self) -> IssueSeverity {
64 | IssueSeverity::Low
65 | }
66 |
67 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
68 | self.found_instances.clone()
69 | }
70 |
71 | fn name(&self) -> String {
72 | format!("{}", IssueDetectorNamePool::EmptyBlock)
73 | }
74 | }
75 |
76 | #[cfg(test)]
77 | mod empty_block_tests {
78 |
79 | use crate::detect::{detector::IssueDetector, test_utils::load_solidity_source_unit};
80 |
81 | use super::EmptyBlockDetector;
82 |
83 | #[test]
84 |
85 | fn test_empty_block_by_loading_contract_directly() {
86 | let context = load_solidity_source_unit("../tests/contract-playground/src/EmptyBlocks.sol");
87 |
88 | let mut detector = EmptyBlockDetector::default();
89 | let found = detector.detect(&context).unwrap();
90 | assert!(found);
91 | assert_eq!(detector.instances().len(), 7);
92 | }
93 | }
94 |
```
--------------------------------------------------------------------------------
/aderyn_driver/src/interface/util.rs:
--------------------------------------------------------------------------------
```rust
1 | /*
2 | * Helps you carve a path from one file to another
3 | *
4 | * use case:
5 | * Wherever you chose to keep report.md, you need a rel. pathway to link back
6 | * to solidity file. This is important because absolute paths are out of scope
7 | * if you want to embed them as links in markdown.
8 | */
9 |
10 | use std::{
11 | collections::HashSet,
12 | path::{Component, PathBuf},
13 | };
14 |
15 | use aderyn_core::{ast::SourceUnit, context::workspace::WorkspaceContext, report::*};
16 |
17 | pub fn carve_shortest_path(from_file: PathBuf, to_file: PathBuf) -> PathBuf {
18 | assert!(from_file.exists());
19 | assert!(to_file.exists());
20 | assert!(from_file.is_file());
21 | assert!(to_file.is_file());
22 | assert!(from_file.is_absolute());
23 | assert!(to_file.is_absolute());
24 |
25 | let mut to_file_comps = to_file.components();
26 | let mut from_file_comps = from_file.components();
27 |
28 | // curr_tfc - `current` `t`o_`f`ile `c`omponent
29 | let mut curr_tfc = to_file_comps.next();
30 |
31 | // curr_ffc - `current` `f`rom_`f`ile `c`omponent
32 | let mut curr_ffc = from_file_comps.next();
33 |
34 | let mut buffer = PathBuf::new();
35 |
36 | // Hold the max length common starting path in the buffer
37 | while let (Some(tfc), Some(ffc)) = (curr_tfc, curr_ffc) {
38 | if tfc != ffc {
39 | break;
40 | }
41 | buffer.push(ffc);
42 | curr_tfc = to_file_comps.next();
43 | curr_ffc = from_file_comps.next();
44 | }
45 |
46 | // Now, we are at the common place
47 |
48 | // High level 2 step plan to get to the `to_file`
49 | // 1. Do '../' until you reach a common place |==> you can reverse this problem (since we only
50 | // care about no. of steps) |==> ask how many directories forward you should go to reach
51 | // `from_file` |==> That's how many times you must come back!
52 | // 2. Now, go forward till you reach the `to_file`
53 |
54 | // STEP 1
55 | // Calculate '../' count
56 | let mut count_back = 0;
57 |
58 | // Keep looking forward until you reach the to_file
59 | while let Some(ffc) = curr_ffc {
60 | buffer.push(ffc);
61 | if let Component::Normal(_) = ffc
62 | && buffer.is_file()
63 | {
64 | break;
65 | }
66 | count_back += 1;
67 | curr_ffc = from_file_comps.next();
68 | }
69 |
70 | let mut backward_comps = (0..count_back).map(|_| Component::ParentDir).collect::<Vec<_>>();
71 |
72 | // STEP 2
73 | // Now, let's capture the forward path for `to_file`
74 | let mut forward_comps = vec![];
75 |
76 | while let Some(comp) = curr_tfc {
77 | forward_comps.push(comp);
78 | curr_tfc = to_file_comps.next();
79 | }
80 |
81 | // Finally, concatenate both components
82 | backward_comps.extend(forward_comps.iter());
83 |
84 | backward_comps.iter().map(|c| c.as_os_str()).collect::<PathBuf>()
85 | }
86 |
87 | pub fn files_details(context: &WorkspaceContext) -> FilesDetails {
88 | let sloc_stats = &context.sloc_stats;
89 |
90 | let mut source_units = context.source_units_context.clone();
91 | source_units
92 | .sort_by_key(|su: &SourceUnit| su.absolute_path.as_deref().unwrap_or("").to_string());
93 |
94 | let mut seen_paths = HashSet::new();
95 | let files_details = source_units
96 | .iter()
97 | .filter_map(|source_unit| {
98 | let filepath = source_unit.absolute_path.as_ref()?;
99 | if seen_paths.insert(filepath.clone()) {
100 | let report = sloc_stats.iter().find(|r| r.0.contains(filepath))?;
101 | Some(FilesDetail { file_path: filepath.to_owned(), n_sloc: *report.1 })
102 | } else {
103 | None
104 | }
105 | })
106 | .collect::<Vec<_>>();
107 |
108 | FilesDetails { files_details }
109 | }
110 |
```
--------------------------------------------------------------------------------
/tests/ast/mappings.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"C":[19]},"id":20,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":19,"linearizedBaseContracts":[19],"name":"C","nameLocation":"9:1:1","nodeType":"ContractDefinition","nodes":[{"canonicalName":"C.E","id":4,"members":[{"id":1,"name":"A","nameLocation":"26:1:1","nodeType":"EnumValue","src":"26:1:1"},{"id":2,"name":"B","nameLocation":"29:1:1","nodeType":"EnumValue","src":"29:1:1"},{"id":3,"name":"C","nameLocation":"32:1:1","nodeType":"EnumValue","src":"32:1:1"}],"name":"E","nameLocation":"22:1:1","nodeType":"EnumDefinition","src":"17:18:1"},{"constant":false,"id":9,"mutability":"mutable","name":"a","nameLocation":"59:1:1","nodeType":"VariableDeclaration","scope":19,"src":"40:20:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_mapping$_t_contract$_C_$19_$_t_bool_$","typeString":"mapping(contract C => bool)"},"typeName":{"id":8,"keyType":{"id":6,"nodeType":"UserDefinedTypeName","pathNode":{"id":5,"name":"C","nameLocations":["48:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":19,"src":"48:1:1"},"referencedDeclaration":19,"src":"48:1:1","typeDescriptions":{"typeIdentifier":"t_contract$_C_$19","typeString":"contract C"}},"nodeType":"Mapping","src":"40:18:1","typeDescriptions":{"typeIdentifier":"t_mapping$_t_contract$_C_$19_$_t_bool_$","typeString":"mapping(contract C => bool)"},"valueType":{"id":7,"name":"bool","nodeType":"ElementaryTypeName","src":"53:4:1","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}},"visibility":"internal"},{"constant":false,"id":13,"mutability":"mutable","name":"b","nameLocation":"91:1:1","nodeType":"VariableDeclaration","scope":19,"src":"66:26:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_bool_$","typeString":"mapping(address => bool)"},"typeName":{"id":12,"keyType":{"id":10,"name":"address","nodeType":"ElementaryTypeName","src":"74:7:1","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Mapping","src":"66:24:1","typeDescriptions":{"typeIdentifier":"t_mapping$_t_address_$_t_bool_$","typeString":"mapping(address => bool)"},"valueType":{"id":11,"name":"bool","nodeType":"ElementaryTypeName","src":"85:4:1","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}},"visibility":"internal"},{"constant":false,"id":18,"mutability":"mutable","name":"c","nameLocation":"117:1:1","nodeType":"VariableDeclaration","scope":19,"src":"98:20:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_mapping$_t_enum$_E_$4_$_t_bool_$","typeString":"mapping(enum C.E => bool)"},"typeName":{"id":17,"keyType":{"id":15,"nodeType":"UserDefinedTypeName","pathNode":{"id":14,"name":"E","nameLocations":["106:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":4,"src":"106:1:1"},"referencedDeclaration":4,"src":"106:1:1","typeDescriptions":{"typeIdentifier":"t_enum$_E_$4","typeString":"enum C.E"}},"nodeType":"Mapping","src":"98:18:1","typeDescriptions":{"typeIdentifier":"t_mapping$_t_enum$_E_$4_$_t_bool_$","typeString":"mapping(enum C.E => bool)"},"valueType":{"id":16,"name":"bool","nodeType":"ElementaryTypeName","src":"111:4:1","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}},"visibility":"internal"}],"scope":20,"src":"0:121:1","usedErrors":[]}],"src":"0:122:1"}
2 |
```
--------------------------------------------------------------------------------
/.github/workflows/cargo.yml:
--------------------------------------------------------------------------------
```yaml
1 | on: [push, pull_request, workflow_dispatch]
2 |
3 | name: Aderyn
4 |
5 | concurrency:
6 | group: ci-${{ github.ref }}-cargo
7 | cancel-in-progress: true
8 |
9 | jobs:
10 | check:
11 | name: Check
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout Sources
15 | uses: actions/checkout@v4
16 |
17 | - name: Install Rust Nightly (2025-01-01)
18 | uses: actions-rs/toolchain@v1
19 | with:
20 | profile: minimal
21 | toolchain: nightly-2025-09-20
22 | override: true
23 |
24 | - name: Restore Rust Cache
25 | uses: Swatinem/rust-cache@v2
26 |
27 | - name: Run checks
28 | run: |
29 | cargo check
30 |
31 | test:
32 | name: Tests
33 | runs-on: ubuntu-latest
34 | steps:
35 | - name: Checkout Sources
36 | uses: actions/checkout@v4
37 |
38 | - name: Checkout repository with submodules
39 | uses: actions/checkout@v4
40 | with:
41 | submodules: recursive
42 |
43 | - name: Cache submodules
44 | id: cache-submodules
45 | uses: actions/cache@v3
46 | with:
47 | path: .git/modules
48 | key: submodules-${{ runner.os }}-${{ hashFiles('.gitmodules') }}
49 | restore-keys: |
50 | submodules-${{ runner.os }}-${{ hashFiles('.gitmodules') }}
51 |
52 | - name: Install Rust Nightly (2025-01-01)
53 | uses: actions-rs/toolchain@v1
54 | with:
55 | profile: minimal
56 | toolchain: nightly-2025-09-20
57 | override: true
58 |
59 | - name: Restore Rust Cache
60 | uses: Swatinem/rust-cache@v2
61 |
62 | - name: Run tests
63 | run: |
64 | cargo build
65 | cargo test -- --test-threads 1
66 |
67 | lints:
68 | name: Lints
69 | runs-on: ubuntu-latest
70 | steps:
71 | - name: Checkout Sources
72 | uses: actions/checkout@v4
73 |
74 | - name: Install Rust Nightly (2025-01-01)
75 | uses: actions-rs/toolchain@v1
76 | with:
77 | profile: minimal
78 | toolchain: nightly-2025-09-20
79 | override: true
80 |
81 | - name: Run cargo fmt
82 | run: |
83 | rustup component add --toolchain nightly-2025-09-20-x86_64-unknown-linux-gnu rustfmt
84 | cargo fmt --all --check
85 |
86 | - name: Run cargo clippy
87 | run: |
88 | rustup component add --toolchain nightly-2025-09-20-x86_64-unknown-linux-gnu clippy
89 | cargo clippy -- -D warnings
90 |
91 |
92 | decline-openssl-dependencies:
93 | name: Decline openssl
94 | runs-on: ubuntu-latest
95 | steps:
96 |
97 | - name: Install Rust Nightly (2025-01-01)
98 | uses: actions-rs/toolchain@v1
99 | with:
100 | profile: minimal
101 | toolchain: nightly-2025-09-20
102 | override: true
103 |
104 | - name: Checkout Sources
105 | uses: actions/checkout@v4
106 |
107 | - name: Restore Rust Cache
108 | uses: Swatinem/rust-cache@v2
109 |
110 | - name: Run tests
111 | run: |
112 | ! cargo tree -i openssl --target all
113 |
114 | validate:
115 | name: validate
116 | runs-on: ubuntu-latest
117 | timeout-minutes: 10
118 | steps:
119 | - name: Checkout repository
120 | uses: actions/checkout@v4
121 |
122 | - name: Install Rust Nightly (2025-01-01)
123 | uses: actions-rs/toolchain@v1
124 | with:
125 | profile: minimal
126 | toolchain: nightly-2025-09-20
127 | override: true
128 |
129 | - name: Install cargo-deny
130 | run: |
131 | set -e
132 | curl -L https://github.com/EmbarkStudios/cargo-deny/releases/download/0.14.18/cargo-deny-0.14.18-x86_64-unknown-linux-musl.tar.gz | tar xzf -
133 | mv cargo-deny-*-x86_64-unknown-linux-musl/cargo-deny cargo-deny
134 | echo `pwd` >> $GITHUB_PATH
135 | - name: Validate deps
136 | run: |
137 | cargo deny check bans
138 | cargo deny check sources
139 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/incorrect_caret_operator.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{Expression, LiteralKind, Mutability, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::workspace::{ASTNode, WorkspaceContext},
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct IncorrectUseOfCaretOperatorDetector {
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 IncorrectUseOfCaretOperatorDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | // Copied Heuristic from Slither:
22 | // look for binary expressions with ^ operator where at least one of the operands is a
23 | // constant, and # the constant is not in hex, because hex typically is used with
24 | // bitwise xor and not exponentiation
25 |
26 | for binary_operation in
27 | context.binary_operations().into_iter().filter(|op| op.operator == "^")
28 | {
29 | for expr in [
30 | binary_operation.left_expression.as_ref(),
31 | binary_operation.right_expression.as_ref(),
32 | ] {
33 | if let Expression::Literal(literal) = expr
34 | && literal.kind == LiteralKind::Number
35 | && literal.value.as_ref().is_some_and(|v| !v.starts_with("0x"))
36 | {
37 | capture!(self, context, binary_operation);
38 | }
39 | if let Expression::Identifier(identifier) = expr
40 | && let Some(ref_decl) = identifier.referenced_declaration
41 | && let Some(ASTNode::VariableDeclaration(v)) = context.nodes.get(&ref_decl)
42 | && v.mutability() == Some(&Mutability::Constant)
43 | && let Some(Expression::Literal(literal)) = v.value.as_ref()
44 | && literal.kind == LiteralKind::Number
45 | && literal.value.as_ref().is_some_and(|v| !v.starts_with("0x"))
46 | {
47 | capture!(self, context, binary_operation);
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("Incorrect use of caret operator")
61 | }
62 |
63 | fn description(&self) -> String {
64 | String::from(
65 | "The caret operator is usually mistakenly thought of as an exponentiation operator but actually, it's a bitwise xor operator.",
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::IncorrectCaretOperator.to_string()
75 | }
76 | }
77 |
78 | #[cfg(test)]
79 | mod incorrect_use_of_caret_operator_tests {
80 |
81 | use crate::detect::{detector::IssueDetector, high::IncorrectUseOfCaretOperatorDetector};
82 |
83 | #[test]
84 |
85 | fn test_incorrect_use_of_operator_detector() {
86 | let context = crate::detect::test_utils::load_solidity_source_unit(
87 | "../tests/contract-playground/src/IncorrectCaretOperator.sol",
88 | );
89 |
90 | let mut detector = IncorrectUseOfCaretOperatorDetector::default();
91 | let found = detector.detect(&context).unwrap();
92 |
93 | assert!(found);
94 | assert_eq!(detector.instances().len(), 5);
95 | }
96 | }
97 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unsafe_oz_erc721_mint.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 UnsafeERC721MintDetector {
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 UnsafeERC721MintDetector {
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 == "_mint"
36 | {
37 | let this_contract_definition = identifier
38 | .closest_ancestor_of_type(context, NodeType::ContractDefinition)
39 | .unwrap();
40 | if let ASTNode::ContractDefinition(contract_definition) =
41 | this_contract_definition
42 | {
43 | for base_contract in contract_definition.base_contracts.iter() {
44 | if let Some(base_name) = base_contract.base_name.name()
45 | && base_name.contains("ERC721")
46 | {
47 | capture!(self, context, identifier);
48 | }
49 | }
50 | }
51 | }
52 | }
53 | }
54 | Ok(!self.found_instances.is_empty())
55 | }
56 |
57 | fn title(&self) -> String {
58 | String::from("Unsafe `ERC721::_mint()`")
59 | }
60 |
61 | fn description(&self) -> String {
62 | String::from(
63 | "Using `ERC721::_mint()` can mint ERC721 tokens to addresses which don't support ERC721 tokens. Use `_safeMint()` instead of `_mint()` for ERC721 tokens.",
64 | )
65 | }
66 |
67 | fn severity(&self) -> IssueSeverity {
68 | IssueSeverity::Low
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::UnsafeOzERC721Mint)
77 | }
78 | }
79 |
80 | #[cfg(test)]
81 | mod unsafe_erc721_mint_tests {
82 | use crate::detect::{detector::IssueDetector, low::UnsafeERC721MintDetector};
83 |
84 | #[test]
85 |
86 | fn test_unsafe_erc721_mint_detector_by_loading_contract_directly() {
87 | let context = crate::detect::test_utils::load_solidity_source_unit(
88 | "../tests/contract-playground/src/UnsafeERC721Mint.sol",
89 | );
90 |
91 | let mut detector = UnsafeERC721MintDetector::default();
92 | let found = detector.detect(&context).unwrap();
93 | assert!(found);
94 | assert_eq!(detector.instances().len(), 1);
95 | }
96 | }
97 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/storage_array_memory_edit.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{NodeID, StorageLocation};
4 |
5 | use crate::{
6 | capture,
7 | context::workspace::{ASTNode, WorkspaceContext},
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct StorageArrayMemoryEditDetector {
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 StorageArrayMemoryEditDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | // get all Identifiers with argumentTypes
22 | // If any of them are of the type storage,
23 | // grab the index of that param in the array of argument types
24 | // get the referreddeclaration node of the identifier (a function)
25 | // get parameter at that index and check if the storageLocation is not storage
26 | // if not, capture it.
27 |
28 | for identifier in context
29 | .identifiers()
30 | .into_iter()
31 | .filter(|identifier| identifier.argument_types.is_some())
32 | {
33 | for (index, argument_type) in
34 | identifier.argument_types.as_ref().unwrap().iter().enumerate()
35 | {
36 | if let Some(type_string) = &argument_type.type_string
37 | && type_string.contains("storage ref")
38 | {
39 | let definition_ast =
40 | context.nodes.get(&identifier.referenced_declaration.unwrap());
41 | if let Some(ASTNode::FunctionDefinition(definition)) = definition_ast {
42 | let parameter = definition
43 | .parameters
44 | .parameters
45 | .get(index)
46 | .ok_or_else(|| eyre::eyre!("Parameter not found"))?;
47 | if parameter.storage_location != StorageLocation::Storage {
48 | capture!(self, context, identifier);
49 | }
50 | }
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("Storage Array Edited with Memory")
64 | }
65 |
66 | fn description(&self) -> String {
67 | String::from(
68 | "Storage reference is passed to a function with a memory parameter. This will not update the storage variable as expected. Consider using storage parameters instead.",
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::StorageArrayMemoryEdit.to_string()
78 | }
79 | }
80 |
81 | #[cfg(test)]
82 | mod storage_array_edit_with_memory_tests {
83 |
84 | use crate::detect::{
85 | detector::IssueDetector, high::storage_array_memory_edit::StorageArrayMemoryEditDetector,
86 | };
87 |
88 | #[test]
89 |
90 | fn test_storage_array_edit_with_memory() {
91 | let context = crate::detect::test_utils::load_solidity_source_unit(
92 | "../tests/contract-playground/src/StorageParameters.sol",
93 | );
94 |
95 | let mut detector = StorageArrayMemoryEditDetector::default();
96 | let found = detector.detect(&context).unwrap();
97 | assert!(found);
98 | assert_eq!(detector.instances().len(), 1);
99 | }
100 | }
101 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/graph/callgraph/new.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{
2 | ast::{NodeID, NodeType},
3 | context::{
4 | browser::{ExtractFunctionCalls, ExtractModifierInvocations, GetClosestAncestorOfTypeX},
5 | workspace::{ASTNode, WorkspaceContext},
6 | },
7 | };
8 | use std::collections::{HashMap, HashSet, hash_map::*};
9 |
10 | #[derive(Debug, Default)]
11 | pub struct CallgraphExplorationPoints {
12 | pub points: HashMap<NodeID, GraphPoints>,
13 | }
14 |
15 | #[derive(Debug, Default)]
16 | pub struct GraphPoints {
17 | pub entry: HashSet<NodeID>,
18 | pub inward: HashSet<NodeID>,
19 | pub outward: HashSet<NodeID>,
20 | }
21 |
22 | pub enum GraphPointType {
23 | Entry,
24 | Inward,
25 | Outward,
26 | }
27 |
28 | impl CallgraphExplorationPoints {
29 | pub fn new() -> Self {
30 | Default::default()
31 | }
32 | pub fn add(&mut self, graph_type: GraphPointType, contract_id: NodeID, point: NodeID) {
33 | match self.points.entry(contract_id) {
34 | Entry::Occupied(mut o) => match graph_type {
35 | GraphPointType::Entry => {
36 | o.get_mut().entry.insert(point);
37 | }
38 | GraphPointType::Inward => {
39 | o.get_mut().inward.insert(point);
40 | }
41 | GraphPointType::Outward => {
42 | o.get_mut().outward.insert(point);
43 | }
44 | },
45 | Entry::Vacant(v) => {
46 | v.insert(Default::default());
47 | self.add(graph_type, contract_id, point);
48 | }
49 | };
50 | }
51 | }
52 |
53 | pub(super) fn derive_surface_points(
54 | context: &WorkspaceContext,
55 | nodes: &[&ASTNode],
56 | ) -> CallgraphExplorationPoints {
57 | let mut cg_points = CallgraphExplorationPoints::new();
58 |
59 | let containing_function_or_modifier = |node: &ASTNode| -> Option<NodeID> {
60 | if matches!(node.node_type(), NodeType::FunctionDefinition | NodeType::ModifierDefinition) {
61 | return node.id();
62 | }
63 | node.closest_ancestor_of_type(context, NodeType::FunctionDefinition)
64 | .or_else(|| node.closest_ancestor_of_type(context, NodeType::ModifierDefinition))?
65 | .id()
66 | };
67 |
68 | for &node in nodes {
69 | let Some(containing_fm) = containing_function_or_modifier(node) else {
70 | continue;
71 | };
72 | let function_calls = ExtractFunctionCalls::from(node).extracted;
73 | let modifier_calls = ExtractModifierInvocations::from(node).extracted;
74 |
75 | let cg = context.callgraphs.as_ref().expect("callgraph not found");
76 | for (contract_id, graph) in &cg.inward_callgraphs {
77 | let Some(ASTNode::ContractDefinition(contract)) = context.nodes.get(contract_id) else {
78 | continue;
79 | };
80 | if graph.contains_key(&containing_fm) {
81 | // Entry
82 | if let Some(node_id) = node.id() {
83 | cg_points.add(GraphPointType::Entry, *contract_id, node_id);
84 | }
85 | // Inward
86 | for function_call in function_calls.iter() {
87 | if let Some(f) = context.resolve_internal_call(contract, function_call) {
88 | cg_points.add(GraphPointType::Inward, *contract_id, f.id);
89 | }
90 | }
91 | for modifier_call in modifier_calls.iter() {
92 | if let Some(m) = context.resolve_modifier_call(contract, modifier_call) {
93 | cg_points.add(GraphPointType::Inward, *contract_id, m.id);
94 | }
95 | }
96 | // Outward
97 | cg_points.add(GraphPointType::Outward, *contract_id, containing_fm);
98 | }
99 | }
100 | }
101 | cg_points
102 | }
103 |
```
--------------------------------------------------------------------------------
/tests/ast/try_catch.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"src/X.sol","id":40896,"exportedSymbols":{"SomeContract":[40895]},"nodeType":"SourceUnit","src":"0:243:17","nodes":[{"id":40895,"nodeType":"ContractDefinition","src":"0:243:17","nodes":[{"id":40894,"nodeType":"FunctionDefinition","src":"28:213:17","nodes":[],"body":{"id":40893,"nodeType":"Block","src":"50:191:17","nodes":[],"statements":[{"clauses":[{"block":{"id":40880,"nodeType":"Block","src":"75:30:17","statements":[]},"errorName":"","id":40881,"nodeType":"TryCatchClause","src":"75:30:17"},{"block":{"id":40885,"nodeType":"Block","src":"140:30:17","statements":[]},"errorName":"Error","id":40886,"nodeType":"TryCatchClause","parameters":{"id":40884,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40883,"mutability":"mutable","name":"reason","nameLocation":"132:6:17","nodeType":"VariableDeclaration","scope":40886,"src":"118:20:17","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":40882,"name":"string","nodeType":"ElementaryTypeName","src":"118:6:17","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"visibility":"internal"}],"src":"117:22:17"},"src":"106:64:17"},{"block":{"id":40890,"nodeType":"Block","src":"205:30:17","statements":[]},"errorName":"","id":40891,"nodeType":"TryCatchClause","parameters":{"id":40889,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40888,"mutability":"mutable","name":"lowLevelData","nameLocation":"191:12:17","nodeType":"VariableDeclaration","scope":40891,"src":"178:25:17","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":40887,"name":"bytes","nodeType":"ElementaryTypeName","src":"178:5:17","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"177:27:17"},"src":"171:64:17"}],"externalCall":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":40877,"name":"this","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-28,"src":"64:4:17","typeDescriptions":{"typeIdentifier":"t_contract$_SomeContract_$40895","typeString":"contract SomeContract"}},"id":40878,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"69:3:17","memberName":"foo","nodeType":"MemberAccess","referencedDeclaration":40894,"src":"64:8:17","typeDescriptions":{"typeIdentifier":"t_function_external_nonpayable$__$returns$__$","typeString":"function () external"}},"id":40879,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"64:10:17","tryCall":true,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":40892,"nodeType":"TryStatement","src":"60:175:17"}]},"functionSelector":"c2985578","implemented":true,"kind":"function","modifiers":[],"name":"foo","nameLocation":"37:3:17","parameters":{"id":40875,"nodeType":"ParameterList","parameters":[],"src":"40:2:17"},"returnParameters":{"id":40876,"nodeType":"ParameterList","parameters":[],"src":"50:0:17"},"scope":40895,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[],"canonicalName":"SomeContract","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[40895],"name":"SomeContract","nameLocation":"9:12:17","scope":40896,"usedErrors":[],"usedEvents":[]}]}
```
--------------------------------------------------------------------------------
/reports/nft-report.md:
--------------------------------------------------------------------------------
```markdown
1 | # Aderyn Analysis Report
2 |
3 | This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a static analysis tool built by [Cyfrin](https://cyfrin.io), a blockchain security company. This report is not a substitute for manual audit or security review. It should not be relied upon for any purpose other than to assist in the identification of potential security vulnerabilities.
4 | # Table of Contents
5 |
6 | - [Summary](#summary)
7 | - [Files Summary](#files-summary)
8 | - [Files Details](#files-details)
9 | - [Issue Summary](#issue-summary)
10 | - [Low Issues](#low-issues)
11 | - [L-1: Unspecific Solidity Pragma](#l-1-unspecific-solidity-pragma)
12 | - [L-2: Public Function Not Used Internally](#l-2-public-function-not-used-internally)
13 | - [L-3: PUSH0 Opcode](#l-3-push0-opcode)
14 | - [L-4: Unused Import](#l-4-unused-import)
15 | - [L-5: State Variable Could Be Constant](#l-5-state-variable-could-be-constant)
16 |
17 |
18 | # Summary
19 |
20 | ## Files Summary
21 |
22 | | Key | Value |
23 | | --- | --- |
24 | | .sol Files | 5 |
25 | | Total nSLOC | 34 |
26 |
27 |
28 | ## Files Details
29 |
30 | | Filepath | nSLOC |
31 | | --- | --- |
32 | | src/BasicNft.sol | 15 |
33 | | src/F1.sol | 4 |
34 | | src/F2.sol | 3 |
35 | | src/Initializer.sol | 7 |
36 | | src/inner-core-modules/ICM.sol | 5 |
37 | | **Total** | **34** |
38 |
39 |
40 | ## Issue Summary
41 |
42 | | Category | No. of Issues |
43 | | --- | --- |
44 | | High | 0 |
45 | | Low | 5 |
46 |
47 |
48 | # Low Issues
49 |
50 | ## L-1: Unspecific Solidity Pragma
51 |
52 | Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;`
53 |
54 | <details><summary>1 Found Instances</summary>
55 |
56 |
57 | - Found in src/F1.sol [Line: 3](../tests/foundry-nft-f23/src/F1.sol#L3)
58 |
59 | ```solidity
60 | pragma solidity ^0.8.10;
61 | ```
62 |
63 | </details>
64 |
65 |
66 |
67 | ## L-2: Public Function Not Used Internally
68 |
69 | If a function is marked public but is not used internally, consider marking it as `external`.
70 |
71 | <details><summary>2 Found Instances</summary>
72 |
73 |
74 | - Found in src/BasicNft.sol [Line: 18](../tests/foundry-nft-f23/src/BasicNft.sol#L18)
75 |
76 | ```solidity
77 | function mint() public {
78 | ```
79 |
80 | - Found in src/Initializer.sol [Line: 8](../tests/foundry-nft-f23/src/Initializer.sol#L8)
81 |
82 | ```solidity
83 | function get_start_token_id() public pure returns(uint256) {
84 | ```
85 |
86 | </details>
87 |
88 |
89 |
90 | ## L-3: PUSH0 Opcode
91 |
92 | Solc compiler version 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain other than mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail.
93 |
94 | <details><summary>1 Found Instances</summary>
95 |
96 |
97 | - Found in src/F1.sol [Line: 3](../tests/foundry-nft-f23/src/F1.sol#L3)
98 |
99 | ```solidity
100 | pragma solidity ^0.8.10;
101 | ```
102 |
103 | </details>
104 |
105 |
106 |
107 | ## L-4: Unused Import
108 |
109 | Redundant import statement. Consider removing it.
110 |
111 | <details><summary>1 Found Instances</summary>
112 |
113 |
114 | - Found in src/F1.sol [Line: 5](../tests/foundry-nft-f23/src/F1.sol#L5)
115 |
116 | ```solidity
117 | import "./F2.sol";
118 | ```
119 |
120 | </details>
121 |
122 |
123 |
124 | ## L-5: State Variable Could Be Constant
125 |
126 | State variables that are not updated following deployment should be declared constant to save gas. Add the `constant` attribute to state variables that never change.
127 |
128 | <details><summary>2 Found Instances</summary>
129 |
130 |
131 | - Found in src/inner-core-modules/ICM.sol [Line: 6](../tests/foundry-nft-f23/src/inner-core-modules/ICM.sol#L6)
132 |
133 | ```solidity
134 | string public PROJECT_NAME = "Dogie";
135 | ```
136 |
137 | - Found in src/inner-core-modules/ICM.sol [Line: 7](../tests/foundry-nft-f23/src/inner-core-modules/ICM.sol#L7)
138 |
139 | ```solidity
140 | string public PROJECT_SYMBOL = "DOG";
141 | ```
142 |
143 | </details>
144 |
145 |
146 |
147 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/ast/impls/disp/contracts.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::ast::*;
2 | use std::fmt::Display;
3 |
4 | impl Display for ContractKind {
5 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6 | f.write_fmt(format_args!("{}", format!("{self:?}").to_lowercase()))
7 | }
8 | }
9 |
10 | impl Display for ContractDefinitionNode {
11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12 | match self {
13 | ContractDefinitionNode::UsingForDirective(using_for_directive) => {
14 | using_for_directive.fmt(f)
15 | }
16 | ContractDefinitionNode::StructDefinition(struct_definition) => struct_definition.fmt(f),
17 | ContractDefinitionNode::EnumDefinition(enum_definition) => enum_definition.fmt(f),
18 | ContractDefinitionNode::VariableDeclaration(variable_declaration) => {
19 | variable_declaration.fmt(f)
20 | }
21 | ContractDefinitionNode::EventDefinition(event_definition) => event_definition.fmt(f),
22 | ContractDefinitionNode::FunctionDefinition(function_definition) => {
23 | function_definition.fmt(f)
24 | }
25 | ContractDefinitionNode::ModifierDefinition(modifier_definition) => {
26 | modifier_definition.fmt(f)
27 | }
28 | ContractDefinitionNode::ErrorDefinition(error_definition) => error_definition.fmt(f),
29 | ContractDefinitionNode::UserDefinedValueTypeDefinition(
30 | user_defined_value_type_definition,
31 | ) => user_defined_value_type_definition.fmt(f),
32 | }
33 | }
34 | }
35 |
36 | impl Display for InheritanceSpecifier {
37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 | f.write_fmt(format_args!("{:?}", self.base_name))?;
39 |
40 | if let Some(arguments) = self.arguments.as_ref() {
41 | f.write_str("(")?;
42 |
43 | for (i, argument) in arguments.iter().enumerate() {
44 | f.write_fmt(format_args!(
45 | "{}{}",
46 | match i {
47 | 0 => "",
48 | _ => ", ",
49 | },
50 | argument,
51 | ))?;
52 | }
53 |
54 | f.write_str(")")?;
55 | }
56 |
57 | Ok(())
58 | }
59 | }
60 |
61 | impl Display for ContractDefinition {
62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 | if self.is_abstract {
64 | f.write_str("abstract ")?;
65 | }
66 |
67 | f.write_fmt(format_args!("{} {}", self.kind, self.name))?;
68 |
69 | for (i, base_contract) in self.base_contracts.iter().enumerate() {
70 | f.write_fmt(format_args!(
71 | "{}{}",
72 | match i {
73 | 0 => " is ",
74 | _ => ", ",
75 | },
76 | base_contract
77 | ))?;
78 | }
79 |
80 | f.write_str(" {\n")?;
81 |
82 | for node in self.nodes.iter() {
83 | f.write_fmt(format_args!(
84 | "\t{}{}\n",
85 | node,
86 | match node {
87 | ContractDefinitionNode::UsingForDirective(_)
88 | | ContractDefinitionNode::EventDefinition(_)
89 | | ContractDefinitionNode::ErrorDefinition(_)
90 | | ContractDefinitionNode::VariableDeclaration(_)
91 | | ContractDefinitionNode::UserDefinedValueTypeDefinition(_) => ";",
92 |
93 | ContractDefinitionNode::StructDefinition(_)
94 | | ContractDefinitionNode::EnumDefinition(_)
95 | | ContractDefinitionNode::FunctionDefinition(_)
96 | | ContractDefinitionNode::ModifierDefinition(_) => "",
97 | }
98 | ))?;
99 | }
100 |
101 | f.write_str("}")?;
102 |
103 | Ok(())
104 | }
105 | }
106 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/delegate_call_unchecked_address.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::NodeID;
4 |
5 | use crate::capture;
6 |
7 | use crate::{
8 | context::{graph::CallGraphVisitor, workspace::WorkspaceContext},
9 | detect::{
10 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
11 | helpers,
12 | },
13 | };
14 | use eyre::Result;
15 |
16 | #[derive(Default)]
17 | pub struct DelegateCallUncheckedAddressDetector {
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 DelegateCallUncheckedAddressDetector {
24 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
25 | struct DelegateCallNoAddressChecksTracker<'a> {
26 | has_address_checks: bool,
27 | has_delegate_call_on_non_state_variable_address: bool,
28 | context: &'a WorkspaceContext,
29 | }
30 |
31 | impl CallGraphVisitor for DelegateCallNoAddressChecksTracker<'_> {
32 | fn visit_any(&mut self, node: &crate::context::workspace::ASTNode) -> eyre::Result<()> {
33 | if !self.has_address_checks && helpers::has_binary_checks_on_some_address(node) {
34 | self.has_address_checks = true;
35 | }
36 | if !self.has_delegate_call_on_non_state_variable_address
37 | && helpers::has_delegate_calls_on_non_state_variables(node, self.context)
38 | {
39 | self.has_delegate_call_on_non_state_variable_address = true;
40 | }
41 | eyre::Ok(())
42 | }
43 | }
44 |
45 | for (func, callgraphs) in context.entrypoints_with_callgraphs() {
46 | for callgraph in callgraphs {
47 | let mut tracker = DelegateCallNoAddressChecksTracker {
48 | has_address_checks: false,
49 | has_delegate_call_on_non_state_variable_address: false,
50 | context,
51 | };
52 | callgraph.accept(context, &mut tracker)?;
53 |
54 | if tracker.has_delegate_call_on_non_state_variable_address
55 | && !tracker.has_address_checks
56 | {
57 | capture!(self, context, func)
58 | }
59 | }
60 | }
61 | Ok(!self.found_instances.is_empty())
62 | }
63 |
64 | fn severity(&self) -> IssueSeverity {
65 | IssueSeverity::High
66 | }
67 |
68 | fn title(&self) -> String {
69 | String::from("`delegatecall` to an Arbitrary Address")
70 | }
71 |
72 | fn description(&self) -> String {
73 | String::from(
74 | "Making a `delegatecall` to an arbitrary address without any checks is dangerous. Consider adding requirements on the target address.",
75 | )
76 | }
77 |
78 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
79 | self.found_instances.clone()
80 | }
81 |
82 | fn name(&self) -> String {
83 | IssueDetectorNamePool::DelegateCallUncheckedAddress.to_string()
84 | }
85 | }
86 |
87 | #[cfg(test)]
88 | mod delegate_call_no_address_check_tests {
89 |
90 | use crate::detect::{
91 | detector::IssueDetector,
92 | high::delegate_call_unchecked_address::DelegateCallUncheckedAddressDetector,
93 | };
94 |
95 | #[test]
96 | fn test_delegate_call_without_checks() {
97 | let context = crate::detect::test_utils::load_solidity_source_unit(
98 | "../tests/contract-playground/src/DelegateCallWithoutAddressCheck.sol",
99 | );
100 |
101 | let mut detector = DelegateCallUncheckedAddressDetector::default();
102 | let found = detector.detect(&context).unwrap();
103 |
104 | assert!(found);
105 |
106 | assert_eq!(detector.instances().len(), 1);
107 | }
108 | }
109 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/require_revert_in_loop.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::{ExtractIdentifiers, ExtractRevertStatements},
9 | graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
10 | workspace::WorkspaceContext,
11 | },
12 | detect::{
13 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
14 | helpers::get_explore_centers_of_loops,
15 | },
16 | };
17 | use eyre::Result;
18 |
19 | #[derive(Default)]
20 | pub struct RequireRevertInLoopDetector {
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 RequireRevertInLoopDetector {
27 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
28 | let loop_explore_centers = get_explore_centers_of_loops(context);
29 |
30 | for l in loop_explore_centers {
31 | let callgraphs = CallGraphConsumer::get(context, &[l], CallGraphDirection::Inward)?;
32 |
33 | for callgraph in callgraphs {
34 | let mut tracker = RevertAndRequireTracker::default();
35 | callgraph.accept(context, &mut tracker)?;
36 |
37 | if tracker.has_require_or_revert || tracker.has_revert_statement {
38 | capture!(self, context, l);
39 | }
40 | }
41 | }
42 |
43 | Ok(!self.found_instances.is_empty())
44 | }
45 |
46 | fn severity(&self) -> IssueSeverity {
47 | IssueSeverity::Low
48 | }
49 |
50 | fn title(&self) -> String {
51 | String::from("Loop Contains `require`/`revert`")
52 | }
53 |
54 | fn description(&self) -> String {
55 | String::from(
56 | "Avoid `require` / `revert` statements in a loop because a single bad item can cause the whole transaction to fail. It's better to forgive on fail and return failed elements post processing of the loop",
57 | )
58 | }
59 |
60 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
61 | self.found_instances.clone()
62 | }
63 |
64 | fn name(&self) -> String {
65 | format!("{}", IssueDetectorNamePool::RequireRevertInLoop)
66 | }
67 | }
68 |
69 | #[derive(Default)]
70 | struct RevertAndRequireTracker {
71 | has_require_or_revert: bool,
72 | has_revert_statement: bool,
73 | }
74 |
75 | impl CallGraphVisitor for RevertAndRequireTracker {
76 | fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
77 | // Check for revert() and require() calls
78 | let identifiers = ExtractIdentifiers::from(node).extracted;
79 |
80 | let requires_and_reverts_are_present =
81 | identifiers.iter().any(|id| id.name == "revert" || id.name == "require");
82 |
83 | if requires_and_reverts_are_present {
84 | self.has_require_or_revert = true;
85 | }
86 |
87 | // Check for revert statements
88 | let revert_statements = ExtractRevertStatements::from(node).extracted;
89 |
90 | if !revert_statements.is_empty() {
91 | self.has_revert_statement = true;
92 | }
93 | Ok(())
94 | }
95 | }
96 |
97 | #[cfg(test)]
98 | mod reevrts_and_requires_in_loops {
99 |
100 | use crate::detect::{
101 | detector::IssueDetector, low::require_revert_in_loop::RequireRevertInLoopDetector,
102 | };
103 |
104 | #[test]
105 |
106 | fn test_reverts_and_requires_in_loops_by_loading_contract_directly() {
107 | let context = crate::detect::test_utils::load_solidity_source_unit(
108 | "../tests/contract-playground/src/RevertsAndRequriesInLoops.sol",
109 | );
110 |
111 | let mut detector = RequireRevertInLoopDetector::default();
112 | let found = detector.detect(&context).unwrap();
113 |
114 | assert!(found);
115 | assert_eq!(detector.instances().len(), 2);
116 | }
117 | }
118 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/builtin_symbol_shadowing.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::NodeID;
4 |
5 | use crate::{capture, detect::detector::IssueDetectorNamePool};
6 | use phf::phf_set;
7 |
8 | use crate::{
9 | context::workspace::WorkspaceContext,
10 | detect::detector::{IssueDetector, IssueSeverity},
11 | };
12 | use eyre::Result;
13 |
14 | #[derive(Default)]
15 | pub struct BuiltinSymbolShadowingDetector {
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 BuiltinSymbolShadowingDetector {
22 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
23 | // Variable Declaration names
24 | for variable_declaration in context.variable_declarations() {
25 | if DENY_LIST.contains(&variable_declaration.name) {
26 | capture!(self, context, variable_declaration);
27 | }
28 | }
29 |
30 | // Function Definition names
31 | for function in context.function_definitions() {
32 | if DENY_LIST.contains(&function.name) {
33 | capture!(self, context, function);
34 | }
35 | }
36 |
37 | // Modifier definition names
38 | for modifier in context.modifier_definitions() {
39 | if DENY_LIST.contains(&modifier.name) {
40 | capture!(self, context, modifier);
41 | }
42 | }
43 |
44 | // Event definition names
45 | for event in context.event_definitions() {
46 | if DENY_LIST.contains(&event.name) {
47 | capture!(self, context, event);
48 | }
49 | }
50 |
51 | Ok(!self.found_instances.is_empty())
52 | }
53 |
54 | fn severity(&self) -> IssueSeverity {
55 | IssueSeverity::Low
56 | }
57 |
58 | fn title(&self) -> String {
59 | String::from("Builtin Symbol Shadowing")
60 | }
61 |
62 | fn description(&self) -> String {
63 | String::from("Name clashes with a built-in-symbol. Consider renaming it.")
64 | }
65 |
66 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
67 | self.found_instances.clone()
68 | }
69 |
70 | fn name(&self) -> String {
71 | format!("{}", IssueDetectorNamePool::BuiltinSymbolShadowing)
72 | }
73 | }
74 |
75 | // Copied from SLITHER
76 | static DENY_LIST: phf::Set<&'static str> = phf_set! {
77 | "assert",
78 | "require",
79 | "revert",
80 | "block",
81 | "blockhash",
82 | "gasleft",
83 | "msg",
84 | "now",
85 | "tx",
86 | "this",
87 | "addmod",
88 | "mulmod",
89 | "keccak256",
90 | "sha256",
91 | "sha3",
92 | "ripemd160",
93 | "ecrecover",
94 | "selfdestruct",
95 | "suicide",
96 | "abi",
97 | "fallback",
98 | "receive",
99 | "abstract",
100 | "after",
101 | "alias",
102 | "apply",
103 | "auto",
104 | "case",
105 | "catch",
106 | "copyof",
107 | "default",
108 | "define",
109 | "final",
110 | "immutable",
111 | "implements",
112 | "in",
113 | "inline",
114 | "let",
115 | "macro",
116 | "match",
117 | "mutable",
118 | "null",
119 | "of",
120 | "override",
121 | "partial",
122 | "promise",
123 | "reference",
124 | "relocatable",
125 | "sealed",
126 | "sizeof",
127 | "static",
128 | "supports",
129 | "switch",
130 | "try",
131 | "type",
132 | "typedef",
133 | "typeof",
134 | "unchecked",
135 | };
136 |
137 | #[cfg(test)]
138 | mod builtin_symbol_shadowing_tests {
139 |
140 | use crate::detect::{
141 | detector::IssueDetector, low::builtin_symbol_shadowing::BuiltinSymbolShadowingDetector,
142 | };
143 |
144 | #[test]
145 |
146 | fn test_builtin_symbol_shadow() {
147 | let context = crate::detect::test_utils::load_solidity_source_unit(
148 | "../tests/contract-playground/src/BuiltinSymbolShadow.sol",
149 | );
150 |
151 | let mut detector = BuiltinSymbolShadowingDetector::default();
152 | let found = detector.detect(&context).unwrap();
153 | assert!(found);
154 | assert_eq!(detector.instances().len(), 4);
155 | }
156 | }
157 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/state_change_without_event.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{FunctionKind, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::{
8 | browser::ExtractEmitStatements,
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 StateVariableChangesWithoutEventDetector {
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 StateVariableChangesWithoutEventDetector {
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 | if *func.kind() == FunctionKind::Constructor {
30 | continue;
31 | }
32 | let callgraphs =
33 | CallGraphConsumer::get(context, &[&(func.into())], CallGraphDirection::Inward)?;
34 |
35 | for callgraph in callgraphs {
36 | let mut tracker = EventEmissionTracker { does_emit_events: false };
37 | callgraph.accept(context, &mut tracker)?;
38 |
39 | if tracker.does_emit_events {
40 | continue;
41 | }
42 |
43 | // At this point, we know that no events are emitted
44 | if let Some(changes) = func.state_variable_changes(context)
45 | && changes.state_variables_have_been_manipulated()
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::Low
57 | }
58 |
59 | fn title(&self) -> String {
60 | String::from("State Change Without Event")
61 | }
62 |
63 | fn description(&self) -> String {
64 | String::from(
65 | "There are state variable changes in this function but no event is emitted. Consider emitting an event to enable offchain indexers to track the changes.",
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 | format!("{}", IssueDetectorNamePool::StateChangeWithoutEvent)
75 | }
76 | }
77 |
78 | struct EventEmissionTracker {
79 | does_emit_events: bool,
80 | }
81 |
82 | impl CallGraphVisitor for EventEmissionTracker {
83 | fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
84 | // Don't bother checking if we already know there is an event emitted
85 | if self.does_emit_events {
86 | return Ok(());
87 | }
88 |
89 | // Check for events
90 | let events = ExtractEmitStatements::from(node).extracted;
91 | if !events.is_empty() {
92 | self.does_emit_events = true;
93 | }
94 | Ok(())
95 | }
96 | }
97 |
98 | #[cfg(test)]
99 | mod state_variable_changes_without_events_tests {
100 |
101 | use crate::detect::{
102 | detector::IssueDetector,
103 | low::state_change_without_event::StateVariableChangesWithoutEventDetector,
104 | };
105 |
106 | #[test]
107 |
108 | fn test_state_variable_changes_without_events() {
109 | let context = crate::detect::test_utils::load_solidity_source_unit(
110 | "../tests/contract-playground/src/StateVariablesChangesWithoutEvents.sol",
111 | );
112 |
113 | let mut detector = StateVariableChangesWithoutEventDetector::default();
114 | let found = detector.detect(&context).unwrap();
115 |
116 | assert!(found);
117 | assert_eq!(detector.instances().len(), 8);
118 | }
119 | }
120 |
```