This is page 6 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
--------------------------------------------------------------------------------
/tests/ast/documentation_3.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"c","exportedSymbols":{"C":[23]},"id":24,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":23,"linearizedBaseContracts":[23],"name":"C","nameLocation":"9:1:3","nodeType":"ContractDefinition","nodes":[{"constant":false,"documentation":{"id":7,"nodeType":"StructuredDocumentation","src":"15:32:3","text":"Some comment on state var."},"functionSelector":"c19d93fb","id":9,"mutability":"mutable","name":"state","nameLocation":"60:5:3","nodeType":"VariableDeclaration","scope":23,"src":"48:17:3","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":8,"name":"uint","nodeType":"ElementaryTypeName","src":"48:4:3","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"public"},{"anonymous":false,"documentation":{"id":10,"nodeType":"StructuredDocumentation","src":"69:26:3","text":"Some comment on Evt."},"eventSelector":"a69007916fc1145953e5a7032d7c3eab4b8e2f33ec59b0f71e732904eeede3a4","id":12,"name":"Evt","nameLocation":"102:3:3","nodeType":"EventDefinition","parameters":{"id":11,"nodeType":"ParameterList","parameters":[],"src":"105:2:3"},"src":"96:12:3"},{"body":{"id":16,"nodeType":"Block","src":"153:6:3","statements":[{"id":15,"nodeType":"PlaceholderStatement","src":"155:1:3"}]},"documentation":{"id":13,"nodeType":"StructuredDocumentation","src":"111:26:3","text":"Some comment on mod."},"id":17,"name":"mod","nameLocation":"147:3:3","nodeType":"ModifierDefinition","parameters":{"id":14,"nodeType":"ParameterList","parameters":[],"src":"150:2:3"},"src":"138:21:3","virtual":false,"visibility":"internal"},{"body":{"id":21,"nodeType":"Block","src":"209:2:3","statements":[]},"documentation":{"id":18,"nodeType":"StructuredDocumentation","src":"162:25:3","text":"Some comment on fn."},"functionSelector":"a4a2c40b","id":22,"implemented":true,"kind":"function","modifiers":[],"name":"fn","nameLocation":"197:2:3","nodeType":"FunctionDefinition","parameters":{"id":19,"nodeType":"ParameterList","parameters":[],"src":"199:2:3"},"returnParameters":{"id":20,"nodeType":"ParameterList","parameters":[],"src":"209:0:3"},"scope":23,"src":"188:23:3","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":24,"src":"0:213:3","usedErrors":[]}],"src":"0:214:3"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/graph/callgraph.rs:
--------------------------------------------------------------------------------
```rust
1 | //! This module helps with strategies on performing different types of investigations.
2 | //!
3 | //! Our first kind of callgraph is [`CallGraph`] it comes bundled with actions to help
4 | //! application modules "hook in" and consume the graphs.
5 |
6 | mod legacy;
7 | mod new;
8 | mod tests;
9 | mod utils;
10 | mod visit;
11 |
12 | use super::{Error, Result, traits::CallGraphVisitor};
13 | use crate::{
14 | ast::NodeID,
15 | context::workspace::{ASTNode, WorkspaceContext},
16 | };
17 |
18 | #[derive(Clone, PartialEq)]
19 | pub enum CallGraphDirection {
20 | /// Deeper into the callgraph
21 | Inward,
22 |
23 | /// Opposite of Inward
24 | Outward,
25 |
26 | /// Both inward and outward (If outward side effects also need to be tracked)
27 | BothWays,
28 | }
29 |
30 | pub struct CallGraphConsumer {
31 | /// Ad-hoc Nodes that we would like to explore inward from.
32 | pub entry_points: Vec<NodeID>,
33 |
34 | /// Surface points are calculated based on the entry points (input)
35 | /// and only consists of [`crate::ast::FunctionDefinition`] and
36 | /// [`crate::ast::ModifierDefinition`] These are nodes that are the *actual* starting
37 | /// points for traversal in the graph
38 | pub inward_surface_points: Vec<NodeID>,
39 |
40 | /// Same as the inward one, but acts on reverse graph.
41 | pub outward_surface_points: Vec<NodeID>,
42 |
43 | /// Decides what graph type to chose.
44 | pub direction: CallGraphDirection,
45 |
46 | /// Decides what graph to chose from [`WorkspaceContext::callgraphs`].
47 | pub base_contract: Option<NodeID>,
48 | }
49 |
50 | #[derive(PartialEq, Clone, Copy)]
51 | enum CurrentDFSVector {
52 | Inward,
53 | Outward,
54 | OutwardSideEffect,
55 | }
56 |
57 | impl CallGraphConsumer {
58 | pub fn get_legacy(
59 | context: &WorkspaceContext,
60 | nodes: &[&ASTNode],
61 | direction: CallGraphDirection,
62 | ) -> super::Result<CallGraphConsumer> {
63 | Self::from_nodes(context, nodes, direction)
64 | }
65 |
66 | pub fn get(
67 | context: &WorkspaceContext,
68 | nodes: &[&ASTNode],
69 | direction: CallGraphDirection,
70 | ) -> super::Result<Vec<CallGraphConsumer>> {
71 | Self::many_from_nodes(context, nodes, direction)
72 | }
73 |
74 | /// Visit the entry points and all the plausible function definitions and modifier definitions
75 | /// that EVM may encounter during execution.
76 | pub fn accept<T>(&self, context: &WorkspaceContext, visitor: &mut T) -> super::Result<()>
77 | where
78 | T: CallGraphVisitor,
79 | {
80 | self._accept(context, visitor)
81 | }
82 |
83 | #[inline]
84 | pub fn is_legacy(&self) -> bool {
85 | self.base_contract.is_none()
86 | }
87 | }
88 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/_template.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::NodeID;
4 |
5 | use crate::{
6 | capture,
7 | context::workspace_context::WorkspaceContext,
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | // HOW TO USE THIS TEMPLATE:
13 | // 1. Copy this file and rename it to the snake_case version of the issue you are detecting.
14 | // 2. Rename the TemplateDetector struct and impl to your new issue name.
15 | // 3. Add this file and detector struct to the mod.rs file in the same directory.
16 | // 4. Implement the detect function to find instances of the issue.
17 |
18 | #[derive(Default)]
19 | pub struct TemplateDetector {
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 | hints: BTreeMap<(String, usize, String), String>,
24 | }
25 |
26 | impl IssueDetector for TemplateDetector {
27 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
28 | // When you have found an instance of the issue,
29 | // use the following macro to add it to `found_instances`:
30 | //
31 | // capture!(self, context, item);
32 | // capture!(self, context, item, "hint");
33 |
34 | Ok(!self.found_instances.is_empty())
35 | }
36 |
37 | fn severity(&self) -> IssueSeverity {
38 | IssueSeverity::High
39 | }
40 |
41 | fn title(&self) -> String {
42 | String::from("High Issue Title")
43 | }
44 |
45 | fn description(&self) -> String {
46 | String::from("Description of the high issue.")
47 | }
48 |
49 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
50 | self.found_instances.clone()
51 | }
52 |
53 | fn hints(&self) -> BTreeMap<(String, usize, String), String> {
54 | self.hints.clone()
55 | }
56 |
57 | fn name(&self) -> String {
58 | format!("high-issue-template")
59 | }
60 | }
61 |
62 | #[cfg(test)]
63 | mod template_detector_tests {
64 | use serial_test::serial;
65 |
66 | use crate::detect::{detector::IssueDetector, high::template_detector::TemplateDetector};
67 |
68 | #[test]
69 | fn test_template_detector() {
70 | let context = crate::detect::test_utils::load_solidity_source_unit(
71 | "../tests/contract-playground/src/ArbitraryTransferFrom.sol",
72 | );
73 |
74 | let mut detector = TemplateDetector::default();
75 | let found = detector.detect(&context).unwrap();
76 | assert!(found);
77 | assert_eq!(detector.instances().len(), 1);
78 | }
79 | }
80 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/ast/impls/node/modifiers.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{ast::*, visitor::ast_visitor::*};
2 | use eyre::Result;
3 |
4 | impl Node for ModifierDefinition {
5 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
6 | if visitor.visit_modifier_definition(self)? {
7 | // TODO: should we implement a string based visitor?
8 | // self.name.accept(visitor)?;
9 | if let Some(body) = &self.body {
10 | body.accept(visitor)?;
11 | }
12 | if let Some(override_specifier) = &self.overrides {
13 | override_specifier.accept(visitor)?;
14 | }
15 | self.parameters.accept(visitor)?;
16 | }
17 | self.accept_metadata(visitor)?;
18 | visitor.end_visit_modifier_definition(self)
19 | }
20 | fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
21 | if let Some(body) = &self.body {
22 | visitor.visit_immediate_children(self.id, vec![body.id])?;
23 | }
24 | if let Some(override_specifier) = &self.overrides {
25 | visitor.visit_immediate_children(self.id, vec![override_specifier.id])?;
26 | }
27 | visitor.visit_immediate_children(self.id, vec![self.parameters.id])?;
28 | Ok(())
29 | }
30 | macros::accept_id!();
31 | }
32 |
33 | impl Node for ModifierInvocation {
34 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
35 | if visitor.visit_modifier_invocation(self)? {
36 | match &self.modifier_name {
37 | IdentifierOrIdentifierPath::Identifier(identifier) => identifier.accept(visitor)?,
38 | IdentifierOrIdentifierPath::IdentifierPath(identifier_path) => {
39 | identifier_path.accept(visitor)?
40 | }
41 | };
42 | if self.arguments.is_some() {
43 | list_accept(self.arguments.as_ref().unwrap(), visitor)?;
44 | }
45 | }
46 | self.accept_metadata(visitor)?;
47 | visitor.end_visit_modifier_invocation(self)
48 | }
49 | fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
50 | visitor.visit_immediate_children(self.id, vec![self.modifier_name.get_node_id()])?;
51 | if let Some(arguments) = &self.arguments {
52 | let mut argument_ids = vec![];
53 | for arg in arguments {
54 | if let Some(arg_id) = arg.get_node_id() {
55 | argument_ids.push(arg_id);
56 | }
57 | }
58 | visitor.visit_immediate_children(self.id, argument_ids)?;
59 | }
60 | Ok(())
61 | }
62 | macros::accept_id!();
63 | }
64 |
```
--------------------------------------------------------------------------------
/tests/ast/long_type_name_identifier.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":[{"constant":false,"id":3,"mutability":"mutable","name":"a","nameLocation":"20:1:1","nodeType":"VariableDeclaration","scope":15,"src":"13:8:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_array$_t_uint256_$dyn_storage","typeString":"uint256[]"},"typeName":{"baseType":{"id":1,"name":"uint","nodeType":"ElementaryTypeName","src":"13:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":2,"nodeType":"ArrayTypeName","src":"13:6:1","typeDescriptions":{"typeIdentifier":"t_array$_t_uint256_$dyn_storage_ptr","typeString":"uint256[]"}},"visibility":"internal"},{"body":{"id":13,"nodeType":"Block","src":"43:25:1","statements":[{"assignments":[10],"declarations":[{"constant":false,"id":10,"mutability":"mutable","name":"b","nameLocation":"60:1:1","nodeType":"VariableDeclaration","scope":13,"src":"45:16:1","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_array$_t_uint256_$dyn_storage_ptr","typeString":"uint256[]"},"typeName":{"baseType":{"id":8,"name":"uint","nodeType":"ElementaryTypeName","src":"45:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":9,"nodeType":"ArrayTypeName","src":"45:6:1","typeDescriptions":{"typeIdentifier":"t_array$_t_uint256_$dyn_storage_ptr","typeString":"uint256[]"}},"visibility":"internal"}],"id":12,"initialValue":{"id":11,"name":"a","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":3,"src":"64:1:1","typeDescriptions":{"typeIdentifier":"t_array$_t_uint256_$dyn_storage","typeString":"uint256[] storage ref"}},"nodeType":"VariableDeclarationStatement","src":"45:20:1"}]},"functionSelector":"26121ff0","id":14,"implemented":true,"kind":"function","modifiers":[],"name":"f","nameLocation":"32:1:1","nodeType":"FunctionDefinition","parameters":{"id":4,"nodeType":"ParameterList","parameters":[],"src":"33:2:1"},"returnParameters":{"id":5,"nodeType":"ParameterList","parameters":[],"src":"43:0:1"},"scope":15,"src":"23:45:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":16,"src":"0:70:1","usedErrors":[]}],"src":"0:71:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/_template.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::collections::BTreeMap;
2 | use std::error::Error;
3 |
4 | use crate::ast::NodeID;
5 |
6 | use crate::capture;
7 | use crate::detect::detector::IssueDetectorNamePool;
8 | use crate::{
9 | context::workspace_context::WorkspaceContext,
10 | detect::detector::{IssueDetector, IssueSeverity},
11 | };
12 | use eyre::Result;
13 |
14 | // HOW TO USE THIS TEMPLATE:
15 | // 1. Copy this file and rename it to the snake_case version of the issue you are detecting.
16 | // 2. Rename the TemplateDetector struct and impl to your new issue name.
17 | // 3. Add this file and detector struct to the mod.rs file in the same directory.
18 | // 4. Implement the detect function to find instances of the issue.
19 |
20 | #[derive(Default)]
21 | pub struct TemplateDetector {
22 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
23 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
24 | found_instances: BTreeMap<(String, usize, String), NodeID>,
25 | hints: BTreeMap<(String, usize, String), String>,
26 | }
27 |
28 | impl IssueDetector for TemplateDetector {
29 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
30 | // When you have found an instance of the issue,
31 | // use the following macro to add it to `found_instances`:
32 | //
33 | // capture!(self, context, item);
34 | // capture!(self, context, item, "hint");
35 |
36 | Ok(!self.found_instances.is_empty())
37 | }
38 |
39 | fn severity(&self) -> IssueSeverity {
40 | IssueSeverity::Low
41 | }
42 |
43 | fn title(&self) -> String {
44 | String::from("Low Issue Title")
45 | }
46 |
47 | fn description(&self) -> String {
48 | String::from("Description of the low issue.")
49 | }
50 |
51 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
52 | self.found_instances.clone()
53 | }
54 |
55 | fn hints(&self) -> BTreeMap<(String, usize, String), String> {
56 | self.hints.clone()
57 | }
58 |
59 | fn name(&self) -> String {
60 | format!("low-issue-template")
61 | }
62 | }
63 |
64 | #[cfg(test)]
65 | mod template_detector_tests {
66 | use serial_test::serial;
67 |
68 | use crate::detect::{detector::IssueDetector, low::template_detector::TemplateDetector};
69 |
70 | #[test]
71 | fn test_template_detector() {
72 | let context = crate::detect::test_utils::load_solidity_source_unit(
73 | "../tests/contract-playground/src/ArbitraryTransferFrom.sol",
74 | );
75 |
76 | let mut detector = TemplateDetector::default();
77 | let found = detector.detect(&context).unwrap();
78 | assert!(found);
79 | assert_eq!(detector.instances().len(), 1);
80 | }
81 | }
82 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unsafe_erc20_operation.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 UnsafeERC20OperationDetector {
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 UnsafeERC20OperationDetector {
19 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
20 | for member_access in context.member_accesses() {
21 | if member_access.expression.as_ref().type_descriptions().is_some_and(|desc| {
22 | desc.type_string.as_ref().is_some_and(|type_string| type_string.contains("ERC20"))
23 | }) && member_access.member_name == "transferFrom"
24 | || member_access.member_name == "approve"
25 | || member_access.member_name == "transfer"
26 | {
27 | capture!(self, context, member_access);
28 | }
29 | }
30 | Ok(!self.found_instances.is_empty())
31 | }
32 |
33 | fn title(&self) -> String {
34 | String::from("Unsafe ERC20 Operation")
35 | }
36 |
37 | fn description(&self) -> String {
38 | String::from(
39 | "ERC20 functions may not behave as expected. For example: return values are not always meaningful. It is recommended to use OpenZeppelin's SafeERC20 library.",
40 | )
41 | }
42 |
43 | fn severity(&self) -> IssueSeverity {
44 | IssueSeverity::Low
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::UnsafeERC20Operation)
53 | }
54 | }
55 |
56 | #[cfg(test)]
57 | mod unsafe_erc20_functions_tests {
58 | use crate::detect::detector::IssueDetector;
59 |
60 | use super::UnsafeERC20OperationDetector;
61 |
62 | #[test]
63 |
64 | fn test_unsafe_erc20_functions_by_loading_contract_directly() {
65 | let context = crate::detect::test_utils::load_solidity_source_unit(
66 | "../tests/contract-playground/src/DeprecatedOZFunctions.sol",
67 | );
68 |
69 | let mut detector = UnsafeERC20OperationDetector::default();
70 | let found = detector.detect(&context).unwrap();
71 | assert!(found);
72 | // failure0, failure1 and failure3
73 | assert_eq!(detector.instances().len(), 5);
74 | }
75 | }
76 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/internal_function_used_once.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::{NodeID, Visibility},
5 | capture,
6 | context::workspace::WorkspaceContext,
7 | detect::{
8 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | helpers::count_identifiers_that_reference_an_id,
10 | },
11 | };
12 | use eyre::Result;
13 |
14 | #[derive(Default)]
15 | pub struct InternalFunctionUsedOnceDetector {
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 InternalFunctionUsedOnceDetector {
22 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
23 | let internal_functions = context.function_definitions().into_iter().filter(|&function| {
24 | matches!(function.visibility, Visibility::Internal) && !function.name.starts_with('_')
25 | });
26 |
27 | for internal_function in internal_functions {
28 | if count_identifiers_that_reference_an_id(context, internal_function.id) == 1 {
29 | capture!(self, context, internal_function);
30 | }
31 | }
32 |
33 | Ok(!self.found_instances.is_empty())
34 | }
35 |
36 | fn title(&self) -> String {
37 | String::from("Internal Function Used Only Once")
38 | }
39 |
40 | fn description(&self) -> String {
41 | String::from(
42 | "Instead of separating the logic into a separate function, consider inlining the logic into the calling function. This can reduce the number of function calls and improve readability.",
43 | )
44 | }
45 |
46 | fn severity(&self) -> IssueSeverity {
47 | IssueSeverity::Low
48 | }
49 |
50 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
51 | self.found_instances.clone()
52 | }
53 |
54 | fn name(&self) -> String {
55 | format!("{}", IssueDetectorNamePool::InternalFunctionUsedOnce)
56 | }
57 | }
58 |
59 | #[cfg(test)]
60 | mod uselss_internal_function {
61 | use crate::detect::detector::IssueDetector;
62 |
63 | use super::InternalFunctionUsedOnceDetector;
64 |
65 | #[test]
66 |
67 | fn test_useless_internal_functions_by_loading_contract_directly() {
68 | let context = crate::detect::test_utils::load_solidity_source_unit(
69 | "../tests/contract-playground/src/InternalFunctions.sol",
70 | );
71 |
72 | let mut detector = InternalFunctionUsedOnceDetector::default();
73 | let found = detector.detect(&context).unwrap();
74 | assert!(found);
75 | assert_eq!(detector.instances().len(), 1);
76 | }
77 | }
78 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/non_reentrant_not_first.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 NonReentrantBeforeOthersDetector {
13 | // Keys are source file name, line number and source location
14 | found_instances: BTreeMap<(String, usize, String), NodeID>,
15 | }
16 |
17 | impl IssueDetector for NonReentrantBeforeOthersDetector {
18 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
19 | let function_definitions = context.function_definitions();
20 | for definition in function_definitions {
21 | if definition.modifiers.len() > 1 {
22 | for (index, modifier) in definition.modifiers.iter().enumerate() {
23 | if modifier.modifier_name.name().to_lowercase().contains("nonreentrant")
24 | && index != 0
25 | {
26 | capture!(self, context, modifier);
27 | }
28 | }
29 | }
30 | }
31 | Ok(!self.found_instances.is_empty())
32 | }
33 |
34 | fn title(&self) -> String {
35 | String::from("`nonReentrant` is Not the First Modifier")
36 | }
37 |
38 | fn description(&self) -> String {
39 | String::from(
40 | "To protect against reentrancy in other modifiers, the `nonReentrant` modifier should be the first modifier in the list of modifiers.",
41 | )
42 | }
43 |
44 | fn severity(&self) -> IssueSeverity {
45 | IssueSeverity::Low
46 | }
47 |
48 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
49 | self.found_instances.clone()
50 | }
51 |
52 | fn name(&self) -> String {
53 | format!("{}", IssueDetectorNamePool::NonReentrantNotFirst)
54 | }
55 | }
56 |
57 | #[cfg(test)]
58 | mod non_reentrant_before_others_tests {
59 |
60 | use crate::detect::{detector::IssueDetector, low::NonReentrantBeforeOthersDetector};
61 |
62 | #[test]
63 |
64 | fn test_non_reentrant_before_others_by_loading_contract_directly() {
65 | let context = crate::detect::test_utils::load_solidity_source_unit(
66 | "../tests/contract-playground/src/AdminContract.sol",
67 | );
68 |
69 | let mut detector = NonReentrantBeforeOthersDetector::default();
70 | let found = detector.detect(&context).unwrap();
71 | assert!(found);
72 | assert_eq!(detector.instances().len(), 1);
73 |
74 | // assert that the line number is 10
75 | let (_, line_number, _) = detector.instances().keys().next().unwrap().clone();
76 | assert_eq!(line_number, 10);
77 | }
78 | }
79 |
```
--------------------------------------------------------------------------------
/tests/ast/mutability.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"C":[10]},"id":11,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":10,"linearizedBaseContracts":[10],"name":"C","nameLocation":"9:1:1","nodeType":"ContractDefinition","nodes":[{"constant":false,"functionSelector":"0dbe671f","id":3,"mutability":"immutable","name":"a","nameLocation":"39:1:1","nodeType":"VariableDeclaration","scope":10,"src":"17:27:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1,"name":"uint","nodeType":"ElementaryTypeName","src":"17:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"value":{"hexValue":"34","id":2,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"43:1:1","typeDescriptions":{"typeIdentifier":"t_rational_4_by_1","typeString":"int_const 4"},"value":"4"},"visibility":"public"},{"constant":true,"functionSelector":"4df7e3d0","id":6,"mutability":"constant","name":"b","nameLocation":"71:1:1","nodeType":"VariableDeclaration","scope":10,"src":"50:26:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":4,"name":"uint","nodeType":"ElementaryTypeName","src":"50:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"value":{"hexValue":"32","id":5,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"75:1:1","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"},"visibility":"public"},{"constant":false,"functionSelector":"c3da42b8","id":9,"mutability":"mutable","name":"c","nameLocation":"94:1:1","nodeType":"VariableDeclaration","scope":10,"src":"82:17:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":7,"name":"uint","nodeType":"ElementaryTypeName","src":"82:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"value":{"hexValue":"33","id":8,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"98:1:1","typeDescriptions":{"typeIdentifier":"t_rational_3_by_1","typeString":"int_const 3"},"value":"3"},"visibility":"public"}],"scope":11,"src":"0:102:1","usedErrors":[]}],"src":"0:103:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/ast/impls/node/types.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{ast::*, visitor::ast_visitor::*};
2 | use eyre::Result;
3 | use macros::accept_id;
4 |
5 | impl Node for TypeName {
6 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
7 | match self {
8 | TypeName::FunctionTypeName(function_type_name) => function_type_name.accept(visitor),
9 | TypeName::ArrayTypeName(array_type_name) => array_type_name.accept(visitor),
10 | TypeName::Mapping(mapping) => mapping.accept(visitor),
11 | TypeName::UserDefinedTypeName(user_defined_type_name) => {
12 | user_defined_type_name.accept(visitor)
13 | }
14 | TypeName::ElementaryTypeName(elementary_type_name) => {
15 | elementary_type_name.accept(visitor)
16 | }
17 | TypeName::Raw(_) => Ok(()),
18 | }
19 | }
20 | fn accept_id(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
21 | visitor.visit_node_id(self.get_node_id())?;
22 | Ok(())
23 | }
24 | }
25 |
26 | impl Node for ElementaryTypeName {
27 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
28 | visitor.visit_elementary_type_name(self)?;
29 | visitor.end_visit_elementary_type_name(self)
30 | }
31 | accept_id!();
32 | }
33 |
34 | impl Node for UserDefinedTypeName {
35 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
36 | if visitor.visit_user_defined_type_name(self)? && self.path_node.is_some() {
37 | self.path_node.as_ref().unwrap().accept(visitor)?;
38 | }
39 | visitor.end_visit_user_defined_type_name(self)
40 | }
41 | accept_id!();
42 | }
43 |
44 | impl Node for FunctionTypeName {
45 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
46 | if visitor.visit_function_type_name(self)? {
47 | self.parameter_types.accept(visitor)?;
48 | self.return_parameter_types.accept(visitor)?;
49 | }
50 | visitor.end_visit_function_type_name(self)
51 | }
52 | accept_id!();
53 | }
54 |
55 | impl Node for ArrayTypeName {
56 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
57 | if visitor.visit_array_type_name(self)? {
58 | self.base_type.accept(visitor)?;
59 | if let Some(length) = self.length.as_ref() {
60 | length.accept(visitor)?;
61 | }
62 | }
63 | visitor.end_visit_array_type_name(self)
64 | }
65 | accept_id!();
66 | }
67 |
68 | impl Node for Mapping {
69 | fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
70 | if visitor.visit_mapping(self)? {
71 | self.key_type.accept(visitor)?;
72 | self.value_type.accept(visitor)?;
73 | }
74 | visitor.end_visit_mapping(self)
75 | }
76 | accept_id!();
77 | }
78 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/flow/kind.rs:
--------------------------------------------------------------------------------
```rust
1 | //! Following are the types of statements that are to be considered when building a
2 | //! Control Flow graph
3 | //!
4 | //! REDUCIBLES
5 | //!
6 | //! Step-in
7 | //! -------
8 | //! Block
9 | //! UncheckedBlock
10 | //!
11 | //! Flow
12 | //! ----
13 | //! DoWhileStatement
14 | //! IfStatement
15 | //! ForStatement
16 | //! WhileStatement
17 | //!
18 | //! ----------------------------
19 | //!
20 | //! PRIMITIVES
21 | //!
22 | //! Substitute
23 | //! ----------
24 | //! PlaceholderStatement
25 | //!
26 | //! Jumper
27 | //! ------
28 | //! Break
29 | //! Continue
30 | //! Return
31 | //!
32 | //! Regular
33 | //! ------
34 | //! EmitStatement
35 | //! RevertStatement
36 | //! ExpressionStatement
37 | //! InlineAssembly
38 | //! VariableDeclarationStatement
39 | //! TryStatement
40 |
41 | use super::CfgNodeDescriptor;
42 |
43 | #[derive(PartialEq, Clone, Copy)]
44 | pub enum CfgNodeKind {
45 | Void,
46 | Reducible,
47 | Primitive,
48 | }
49 |
50 | impl CfgNodeDescriptor {
51 | pub fn kind(&self) -> CfgNodeKind {
52 | match self {
53 | // Void nodes
54 | CfgNodeDescriptor::Start(_) => CfgNodeKind::Void,
55 | CfgNodeDescriptor::End(_) => CfgNodeKind::Void,
56 |
57 | // Primitives
58 | CfgNodeDescriptor::VariableDeclarationStatement(_) => CfgNodeKind::Primitive,
59 | CfgNodeDescriptor::ExpressionStatement(_) => CfgNodeKind::Primitive,
60 | CfgNodeDescriptor::PlaceholderStatement(_) => CfgNodeKind::Primitive,
61 | CfgNodeDescriptor::Break(_) => CfgNodeKind::Primitive,
62 | CfgNodeDescriptor::Continue(_) => CfgNodeKind::Primitive,
63 | CfgNodeDescriptor::Return(_) => CfgNodeKind::Primitive,
64 | CfgNodeDescriptor::EmitStatement(_) => CfgNodeKind::Primitive,
65 | CfgNodeDescriptor::RevertStatement(_) => CfgNodeKind::Primitive,
66 | CfgNodeDescriptor::InlineAssembly(_) => CfgNodeKind::Primitive,
67 | CfgNodeDescriptor::TryStatement(_) => CfgNodeKind::Primitive,
68 | CfgNodeDescriptor::IfStatementCondition(_) => CfgNodeKind::Primitive,
69 | CfgNodeDescriptor::WhileStatementCondition(_) => CfgNodeKind::Primitive,
70 | CfgNodeDescriptor::ForStatementCondition(_) => CfgNodeKind::Primitive,
71 | CfgNodeDescriptor::DoWhileStatementCondition(_) => CfgNodeKind::Primitive,
72 |
73 | // Reducibles
74 | CfgNodeDescriptor::Block(_) => CfgNodeKind::Reducible,
75 | CfgNodeDescriptor::UncheckedBlock(_) => CfgNodeKind::Reducible,
76 | CfgNodeDescriptor::IfStatement(_) => CfgNodeKind::Reducible,
77 | CfgNodeDescriptor::WhileStatement(_) => CfgNodeKind::Reducible,
78 | CfgNodeDescriptor::ForStatement(_) => CfgNodeKind::Reducible,
79 | CfgNodeDescriptor::DoWhileStatement(_) => CfgNodeKind::Reducible,
80 | }
81 | }
82 | }
83 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/large_numeric_literal.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::{LiteralKind, 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 LargeLiteralValueDetector {
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 LargeLiteralValueDetector {
19 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
20 | for numeric_literal in context.literals().iter().filter(|x| x.kind == LiteralKind::Number) {
21 | if let Some(value) = numeric_literal.value.clone() {
22 | // Strip any underscore separators
23 | let value_no_underscores = value.replace('_', "");
24 | let is_huge = value_no_underscores.ends_with("0000");
25 | let is_hex = value_no_underscores.starts_with("0x");
26 | let is_exp = value_no_underscores.contains('e');
27 | if is_huge && !is_hex && !is_exp {
28 | capture!(self, context, numeric_literal);
29 | }
30 | }
31 | }
32 |
33 | Ok(!self.found_instances.is_empty())
34 | }
35 |
36 | fn title(&self) -> String {
37 | String::from("Large Numeric Literal")
38 | }
39 |
40 | fn description(&self) -> String {
41 | String::from(
42 | "Large literal values multiples of 10000 can be replaced with scientific notation.Use `e` notation, for example: `1e18`, instead of its full numeric value.",
43 | )
44 | }
45 |
46 | fn severity(&self) -> IssueSeverity {
47 | IssueSeverity::Low
48 | }
49 |
50 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
51 | self.found_instances.clone()
52 | }
53 |
54 | fn name(&self) -> String {
55 | format!("{}", IssueDetectorNamePool::LargeNumericLiteral)
56 | }
57 | }
58 |
59 | #[cfg(test)]
60 | mod large_literal_values {
61 |
62 | use crate::detect::detector::IssueDetector;
63 |
64 | use super::LargeLiteralValueDetector;
65 |
66 | #[test]
67 |
68 | fn test_large_literal_values_multiples_of_10000_by_loading_contract_directly() {
69 | let context = crate::detect::test_utils::load_solidity_source_unit(
70 | "../tests/contract-playground/src/HugeConstants.sol",
71 | );
72 |
73 | let mut detector = LargeLiteralValueDetector::default();
74 | let found = detector.detect(&context).unwrap();
75 | assert!(found);
76 | assert_eq!(detector.instances().len(), 22);
77 | }
78 | }
79 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unused_error.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{
2 | ast::NodeID,
3 | capture,
4 | context::workspace::WorkspaceContext,
5 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
6 | };
7 | use eyre::Result;
8 | use std::{
9 | collections::{BTreeMap, HashSet},
10 | error::Error,
11 | };
12 |
13 | #[derive(Default)]
14 | pub struct UnusedErrorDetector {
15 | // Keys are source file name and line number
16 | found_instances: BTreeMap<(String, usize, String), NodeID>,
17 | }
18 |
19 | impl IssueDetector for UnusedErrorDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | let error_definitions = context.error_definitions().into_iter().collect::<Vec<_>>();
22 | let mut referenced_ids = HashSet::new();
23 |
24 | //Get all MemberAccess and Identifier nodes where the referenced_declaration is an ID of an
25 | // error definition
26 | for identifier in context.identifiers() {
27 | if let Some(reference_id) = identifier.referenced_declaration {
28 | referenced_ids.insert(reference_id);
29 | }
30 | }
31 | for member_access in context.member_accesses() {
32 | if let Some(reference_id) = member_access.referenced_declaration {
33 | referenced_ids.insert(reference_id);
34 | }
35 | }
36 |
37 | // Identify unused errors by comparing defined and used error IDs
38 | for error_def in error_definitions {
39 | if !referenced_ids.contains(&error_def.id) {
40 | // Capture unused error instances
41 | capture!(self, context, error_def);
42 | }
43 | }
44 | Ok(!self.found_instances.is_empty())
45 | }
46 |
47 | fn title(&self) -> String {
48 | String::from("Unused Error")
49 | }
50 |
51 | fn description(&self) -> String {
52 | String::from("Consider using or removing the unused error.")
53 | }
54 |
55 | fn severity(&self) -> IssueSeverity {
56 | IssueSeverity::Low
57 | }
58 |
59 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
60 | self.found_instances.clone()
61 | }
62 |
63 | fn name(&self) -> String {
64 | format!("{}", IssueDetectorNamePool::UnusedError)
65 | }
66 | }
67 |
68 | #[cfg(test)]
69 | mod unused_error_tests {
70 |
71 | use crate::detect::detector::IssueDetector;
72 |
73 | use super::UnusedErrorDetector;
74 |
75 | #[test]
76 |
77 | fn test_unused_error_detection() {
78 | let context = crate::detect::test_utils::load_solidity_source_unit(
79 | "../tests/contract-playground/src/UnusedError.sol",
80 | );
81 |
82 | let mut detector = UnusedErrorDetector::default();
83 | let found = detector.detect(&context).unwrap();
84 | assert!(found);
85 | assert_eq!(detector.instances().len(), 2);
86 | }
87 | }
88 |
```
--------------------------------------------------------------------------------
/aderyn/templates/aderyn.toml:
--------------------------------------------------------------------------------
```toml
1 | # Aderyn Configuration File
2 | # Help Aderyn work with more granular control
3 |
4 | # DO NOT CHANGE version below. As of now, only 1 is supported
5 | version = 1
6 |
7 | # Read the description carefully and uncomment the examples in each paragraph should you consider using them.
8 |
9 | # Base path for resolving remappings and compiling smart contracts, relative to workspace-root (directory in which the editor is open)
10 | # Most of the time, you want to point it to the directory containing foundry.toml or hardhat.config.js/ts.
11 | root = {}
12 |
13 | # Path of source directory containing the contracts, relative to root (above)
14 | # Aderyn will traverse all the nested files inside to scan and report vulnerabilities found inside.
15 | # - If not specified, Aderyn will try to extract it from the framework that is being used. (Foundry / Hardhat).
16 | # That would be "contracts/" in case of Hardhat and in case of Foundry, it depends on foundry.toml and
17 | # many other factors like FOUNDRY_PROFILE environment variable, etc.
18 | # - If specified, Aderyn will override the above found `src`.
19 | # Example:
20 | # src = "src/"
21 |
22 | # Path segments of contract files to include in the analysis.
23 | # - It can be a partial match like "/interfaces/", which will include all files with "/interfaces/" in the file path.
24 | # Or it can be a full match like "src/counters/Counter.sol", which will include only the file with the exact path.
25 | # - If not specified, all contract files in the source directory will be included.
26 | # Examples:
27 | # include = ["src/counters/Counter.sol", "src/others/"]
28 | # include = ["/interfaces/"]
29 |
30 | # Path segments of contract files to exclude in the analysis.
31 | # - It can be a partial match like "/interfaces/", which will exclude all files with "/interfaces/" in the file path.
32 | # Or it can be a full match like "src/counters/Counter.sol", which will exclude only the file with the exact path.
33 | # - If not specified, no contract files will be excluded.
34 | # Examples:
35 | # exclude = ["src/counters/Counter.sol", "src/others/"]
36 | # exclude = ["/interfaces/"]
37 |
38 | # For advanced use cases, leverage the following
39 |
40 | # Remappings
41 | # - It can be specified in `remappings.txt` within the root folder of the project.
42 | # - If not specified, Aderyn will try to derive the values from foundry.toml (if present.)
43 |
44 | # Environment
45 | # - These are usually all the FOUNDRY_, DAPP_ environment variables that are used during development.
46 | # - For example, if different profiles have different `src` declaration in `foundry.toml`, FOUNDRY_PROFILE can dictate the correct `src` value.
47 | # Env variables and their values can be specified below.
48 |
49 | [env]
50 | # Example:
51 | # FOUNDRY_PROFILE = "default"
52 |
```
--------------------------------------------------------------------------------
/tests/toml/nested_project1/aderyn.toml:
--------------------------------------------------------------------------------
```toml
1 | # Aderyn Configuration File
2 | # Help Aderyn work with more granular control
3 |
4 | # DO NOT CHANGE version below. As of now, only 1 is supported
5 | version = 1
6 |
7 | # Read the description carefully and uncomment the examples in each paragraph should you consider using them.
8 |
9 | # Base path for resolving remappings and compiling smart contracts, relative to workspace-root (directory in which the editor is open)
10 | # Most of the time, you want to point it to the directory containing foundry.toml or hardhat.config.js/ts.
11 | root = "folder2"
12 |
13 | # Path of source directory containing the contracts, relative to root (above)
14 | # Aderyn will traverse all the nested files inside to scan and report vulnerabilities found inside.
15 | # - If not specified, Aderyn will try to extract it from the framework that is being used. (Foundry / Hardhat).
16 | # That would be "contracts/" in case of Hardhat and in case of Foundry, it depends on foundry.toml and
17 | # many other factors like FOUNDRY_PROFILE environment variable, etc.
18 | # - If specified, Aderyn will override the above found `src`.
19 | # Example:
20 | # src = "src/"
21 |
22 | # Path segments of contract files to include in the analysis.
23 | # - It can be a partial match like "/interfaces/", which will include all files with "/interfaces/" in the file path.
24 | # Or it can be a full match like "src/counters/Counter.sol", which will include only the file with the exact path.
25 | # - If not specified, all contract files in the source directory will be included.
26 | # Examples:
27 | # include = ["src/counters/Counter.sol", "src/others/"]
28 | # include = ["/interfaces/"]
29 |
30 | # Path segments of contract files to exclude in the analysis.
31 | # - It can be a partial match like "/interfaces/", which will exclude all files with "/interfaces/" in the file path.
32 | # Or it can be a full match like "src/counters/Counter.sol", which will exclude only the file with the exact path.
33 | # - If not specified, no contract files will be excluded.
34 | # Examples:
35 | # exclude = ["src/counters/Counter.sol", "src/others/"]
36 | # exclude = ["/interfaces/"]
37 |
38 | # For advanced use cases, leverage the following
39 |
40 | # Remappings
41 | # - It can be specified in `remappings.txt` within the root folder of the project.
42 | # - If not specified, Aderyn will try to derive the values from foundry.toml (if present.)
43 |
44 | # Environment
45 | # - These are usually all the FOUNDRY_, DAPP_ environment variables that are used during development.
46 | # - For example, if different profiles have different `src` declaration in `foundry.toml`, FOUNDRY_PROFILE can dictate the correct `src` value.
47 | # Env variables and their values can be specified below.
48 |
49 | [env]
50 | # Example:
51 | # FOUNDRY_PROFILE = "default"
52 |
```
--------------------------------------------------------------------------------
/tests/toml/nested_project2/aderyn.toml:
--------------------------------------------------------------------------------
```toml
1 | # Aderyn Configuration File
2 | # Help Aderyn work with more granular control
3 |
4 | # DO NOT CHANGE version below. As of now, only 1 is supported
5 | version = 1
6 |
7 | # Read the description carefully and uncomment the examples in each paragraph should you consider using them.
8 |
9 | # Base path for resolving remappings and compiling smart contracts, relative to workspace-root (directory in which the editor is open)
10 | # Most of the time, you want to point it to the directory containing foundry.toml or hardhat.config.js/ts.
11 | root = "folder1"
12 |
13 | # Path of source directory containing the contracts, relative to root (above)
14 | # Aderyn will traverse all the nested files inside to scan and report vulnerabilities found inside.
15 | # - If not specified, Aderyn will try to extract it from the framework that is being used. (Foundry / Hardhat).
16 | # That would be "contracts/" in case of Hardhat and in case of Foundry, it depends on foundry.toml and
17 | # many other factors like FOUNDRY_PROFILE environment variable, etc.
18 | # - If specified, Aderyn will override the above found `src`.
19 | # Example:
20 | # src = "src/"
21 |
22 | # Path segments of contract files to include in the analysis.
23 | # - It can be a partial match like "/interfaces/", which will include all files with "/interfaces/" in the file path.
24 | # Or it can be a full match like "src/counters/Counter.sol", which will include only the file with the exact path.
25 | # - If not specified, all contract files in the source directory will be included.
26 | # Examples:
27 | # include = ["src/counters/Counter.sol", "src/others/"]
28 | # include = ["/interfaces/"]
29 |
30 | # Path segments of contract files to exclude in the analysis.
31 | # - It can be a partial match like "/interfaces/", which will exclude all files with "/interfaces/" in the file path.
32 | # Or it can be a full match like "src/counters/Counter.sol", which will exclude only the file with the exact path.
33 | # - If not specified, no contract files will be excluded.
34 | # Examples:
35 | # exclude = ["src/counters/Counter.sol", "src/others/"]
36 | # exclude = ["/interfaces/"]
37 |
38 | # For advanced use cases, leverage the following
39 |
40 | # Remappings
41 | # - It can be specified in `remappings.txt` within the root folder of the project.
42 | # - If not specified, Aderyn will try to derive the values from foundry.toml (if present.)
43 |
44 | # Environment
45 | # - These are usually all the FOUNDRY_, DAPP_ environment variables that are used during development.
46 | # - For example, if different profiles have different `src` declaration in `foundry.toml`, FOUNDRY_PROFILE can dictate the correct `src` value.
47 | # Env variables and their values can be specified below.
48 |
49 | [env]
50 | # Example:
51 | # FOUNDRY_PROFILE = "default"
52 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/modifier_used_only_once.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{
2 | collections::{BTreeMap, HashMap},
3 | error::Error,
4 | };
5 |
6 | use crate::{
7 | ast::NodeID,
8 | capture,
9 | context::workspace::WorkspaceContext,
10 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
11 | };
12 | use eyre::Result;
13 |
14 | #[derive(Default)]
15 | pub struct ModifierUsedOnlyOnceDetector {
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 ModifierUsedOnlyOnceDetector {
22 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
23 | let mut invocations: HashMap<i64, usize> = HashMap::new();
24 |
25 | for inv in context.modifier_invocations() {
26 | if let Some(id) = inv.modifier_name.referenced_declaration() {
27 | match invocations.entry(id) {
28 | std::collections::hash_map::Entry::Occupied(mut o) => *o.get_mut() += 1,
29 | std::collections::hash_map::Entry::Vacant(v) => {
30 | v.insert(1);
31 | }
32 | };
33 | }
34 | }
35 |
36 | for modifier in context.modifier_definitions() {
37 | let count = *invocations.get(&modifier.id).unwrap_or(&0);
38 | if count == 1 {
39 | capture!(self, context, modifier);
40 | }
41 | }
42 |
43 | Ok(!self.found_instances.is_empty())
44 | }
45 |
46 | fn title(&self) -> String {
47 | String::from("Modifier Invoked Only Once")
48 | }
49 |
50 | fn description(&self) -> String {
51 | String::from(
52 | "Consider removing the modifier or inlining the logic into the calling function.",
53 | )
54 | }
55 |
56 | fn severity(&self) -> IssueSeverity {
57 | IssueSeverity::Low
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::ModifierUsedOnlyOnce)
66 | }
67 | }
68 |
69 | #[cfg(test)]
70 | mod useless_modifier_tests {
71 | use crate::detect::detector::IssueDetector;
72 |
73 | use super::ModifierUsedOnlyOnceDetector;
74 |
75 | #[test]
76 |
77 | fn test_useless_modifier_tests_by_loading_contract_directly() {
78 | let context = crate::detect::test_utils::load_solidity_source_unit(
79 | "../tests/contract-playground/src/OnceModifierExample.sol",
80 | );
81 |
82 | let mut detector = ModifierUsedOnlyOnceDetector::default();
83 | let found = detector.detect(&context).unwrap();
84 | assert!(found);
85 | assert_eq!(detector.instances().len(), 1);
86 | }
87 | }
88 |
```
--------------------------------------------------------------------------------
/aderyn_driver/src/mcp.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::process::WorkspaceContextWrapper;
2 |
3 | use aderyn_core::context::mcp::*;
4 | use rmcp::{
5 | ErrorData as McpError, RoleServer, ServerHandler, handler::server::router::tool::ToolRouter,
6 | model::*, service::RequestContext, tool_handler,
7 | };
8 | use std::sync::Arc;
9 |
10 | pub struct McpServer {
11 | tool_router: ToolRouter<Self>,
12 | }
13 |
14 | impl McpServer {
15 | pub fn new(raw_state: WorkspaceContextWrapper) -> Self {
16 | let state = Arc::new(ModelContextProtocolState {
17 | contexts: raw_state.contexts,
18 | root_path: raw_state.root_path,
19 | project_config: raw_state.project_config,
20 | });
21 | let tools = get_all_mcp_tools(state);
22 | let mut tool_router = ToolRouter::new();
23 | tools.into_iter().for_each(|r| tool_router.add_route(r));
24 | Self { tool_router }
25 | }
26 | }
27 |
28 | #[tool_handler]
29 | impl ServerHandler for McpServer {
30 | async fn initialize(
31 | &self,
32 | _request: InitializeRequestParam,
33 | _context: RequestContext<RoleServer>,
34 | ) -> Result<rmcp::model::InitializeResult, McpError> {
35 | Ok(ServerInfo {
36 | protocol_version: ProtocolVersion::V_2024_11_05,
37 | capabilities: ServerCapabilities::builder().enable_tools().build(),
38 | server_info: Implementation::from_build_env(),
39 | instructions: Some(
40 | "Intelligently search for patterns in Solidity codebases. To get started, call the tool guide."
41 | .to_string(),
42 | ),
43 | })
44 | }
45 | }
46 |
47 | #[derive(Clone)]
48 | pub struct SingletonMcpServer(Arc<McpServer>);
49 |
50 | impl SingletonMcpServer {
51 | pub fn new(inner: McpServer) -> Self {
52 | Self(Arc::new(inner))
53 | }
54 | }
55 |
56 | impl ServerHandler for SingletonMcpServer {
57 | fn initialize(
58 | &self,
59 | request: InitializeRequestParam,
60 | context: RequestContext<RoleServer>,
61 | ) -> impl std::future::Future<Output = Result<rmcp::model::InitializeResult, McpError>> + Send + '_
62 | {
63 | let inner = self.0.clone();
64 | async move { inner.initialize(request, context).await }
65 | }
66 |
67 | fn call_tool(
68 | &self,
69 | request: CallToolRequestParam,
70 | context: RequestContext<RoleServer>,
71 | ) -> impl std::future::Future<Output = Result<CallToolResult, McpError>> + Send + '_ {
72 | let inner = self.0.clone();
73 | async move { inner.call_tool(request, context).await }
74 | }
75 |
76 | fn list_tools(
77 | &self,
78 | request: Option<PaginatedRequestParam>,
79 | context: RequestContext<RoleServer>,
80 | ) -> impl std::future::Future<Output = Result<ListToolsResult, McpError>> + Send + '_ {
81 | let inner = self.0.clone();
82 | async move { inner.list_tools(request, context).await }
83 | }
84 | }
85 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/mcp/list_contracts/tool.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::context::{
2 | macros::{mcp_error, mcp_success},
3 | mcp::{
4 | MCPToolNamePool, ModelContextProtocolState, ModelContextProtocolTool,
5 | list_contracts::render::{ContractInfoBuilder, ContractsListBuilder},
6 | },
7 | };
8 | use indoc::indoc;
9 | use rmcp::{
10 | ErrorData as McpError, handler::server::wrapper::Parameters, model::CallToolResult, schemars,
11 | };
12 | use serde::Deserialize;
13 | use std::sync::Arc;
14 |
15 | #[derive(Clone)]
16 | pub struct ListContractsTool {
17 | state: Arc<ModelContextProtocolState>,
18 | }
19 |
20 | #[derive(Deserialize, schemars::JsonSchema)]
21 | pub struct ListContractsPayload {
22 | /// The index of the compilation unit to analyze. Must be a positive integer starting from 1.
23 | /// Use the project overview tool first to see all available compilation units and their
24 | /// indices.
25 | pub compilation_unit_index: usize,
26 | }
27 |
28 | impl ModelContextProtocolTool for ListContractsTool {
29 | type Input = ListContractsPayload;
30 |
31 | fn new(state: Arc<ModelContextProtocolState>) -> Self {
32 | Self { state }
33 | }
34 |
35 | fn name(&self) -> String {
36 | MCPToolNamePool::AderynListContracts.to_string()
37 | }
38 |
39 | fn description(&self) -> String {
40 | indoc! {
41 | "Enumerates deployable contracts within a specific compilation unit. Returns contract names, \
42 | file names (relative to the project root) and node IDs."
43 | }
44 | .to_string()
45 | }
46 |
47 | fn execute(&self, input: Parameters<Self::Input>) -> Result<CallToolResult, McpError> {
48 | let comp_unit_idx = input.0.compilation_unit_index;
49 | if comp_unit_idx < 1 || comp_unit_idx > self.state.contexts.len() {
50 | return mcp_error!(
51 | "invalid value passed for compilation unit - must be in the range [1, {}] inclusive",
52 | self.state.contexts.len()
53 | );
54 | }
55 | let context = self.state.contexts.get(comp_unit_idx - 1).expect("bounds check failed");
56 | let mut contracts_info = vec![];
57 | for contract in context.deployable_contracts() {
58 | let (filepath, _, _) = context.get_node_sort_key_from_capturable(&contract.into());
59 | let contract_info = ContractInfoBuilder::default()
60 | .name(contract.name.clone())
61 | .filepath(filepath)
62 | .node_id(contract.id)
63 | .build()
64 | .expect("failed to build contract info");
65 | contracts_info.push(contract_info);
66 | }
67 |
68 | let contract_list = ContractsListBuilder::default()
69 | .compilation_unit_index(comp_unit_idx)
70 | .contracts_info(contracts_info)
71 | .build()
72 | .expect("failed to build contracts list");
73 |
74 | mcp_success!(contract_list)
75 | }
76 | }
77 |
```
--------------------------------------------------------------------------------
/tests/ast/base_constructor_call.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"A":[7],"C":[17]},"id":18,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"A","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":7,"linearizedBaseContracts":[7],"name":"A","nameLocation":"9:1:1","nodeType":"ContractDefinition","nodes":[{"body":{"id":5,"nodeType":"Block","src":"31:2:1","statements":[]},"id":6,"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","nodeType":"FunctionDefinition","parameters":{"id":3,"nodeType":"ParameterList","parameters":[{"constant":false,"id":2,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":6,"src":"25:4:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1,"name":"uint","nodeType":"ElementaryTypeName","src":"25:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"24:6:1"},"returnParameters":{"id":4,"nodeType":"ParameterList","parameters":[],"src":"31:0:1"},"scope":7,"src":"13:20:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":18,"src":"0:35:1","usedErrors":[]},{"abstract":false,"baseContracts":[{"baseName":{"id":8,"name":"A","nameLocations":["50:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":7,"src":"50:1:1"},"id":9,"nodeType":"InheritanceSpecifier","src":"50:1:1"}],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":17,"linearizedBaseContracts":[17,7],"name":"C","nameLocation":"45:1:1","nodeType":"ContractDefinition","nodes":[{"body":{"id":15,"nodeType":"Block","src":"73:2:1","statements":[]},"id":16,"implemented":true,"kind":"constructor","modifiers":[{"arguments":[{"hexValue":"32","id":12,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"70:1:1","typeDescriptions":{"typeIdentifier":"t_rational_2_by_1","typeString":"int_const 2"},"value":"2"}],"id":13,"kind":"baseConstructorSpecifier","modifierName":{"id":11,"name":"A","nameLocations":["68:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":7,"src":"68:1:1"},"nodeType":"ModifierInvocation","src":"68:4:1"}],"name":"","nameLocation":"-1:-1:-1","nodeType":"FunctionDefinition","parameters":{"id":10,"nodeType":"ParameterList","parameters":[],"src":"65:2:1"},"returnParameters":{"id":14,"nodeType":"ParameterList","parameters":[],"src":"73:0:1"},"scope":17,"src":"54:21:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":18,"src":"36:41:1","usedErrors":[]}],"src":"0:78:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/empty_require_revert.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 EmptyRequireRevertDetector {
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 EmptyRequireRevertDetector {
19 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
20 | // Collect all require statements without a string literal.
21 | let requires_and_reverts = context
22 | .identifiers()
23 | .into_iter()
24 | .filter(|&id| id.name == "revert" || id.name == "require");
25 |
26 | for id in requires_and_reverts {
27 | if (id.name == "revert" && id.argument_types.as_ref().unwrap().is_empty())
28 | || (id.name == "require" && id.argument_types.as_ref().unwrap().len() == 1)
29 | {
30 | capture!(self, context, id);
31 | }
32 | }
33 |
34 | Ok(!self.found_instances.is_empty())
35 | }
36 |
37 | fn title(&self) -> String {
38 | String::from("Empty `require()` / `revert()` Statement")
39 | }
40 |
41 | fn description(&self) -> String {
42 | String::from("Use descriptive reason strings or custom errors for revert paths.")
43 | }
44 |
45 | fn severity(&self) -> IssueSeverity {
46 | IssueSeverity::Low
47 | }
48 |
49 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
50 | self.found_instances.clone()
51 | }
52 |
53 | fn name(&self) -> String {
54 | format!("{}", IssueDetectorNamePool::EmptyRequireRevert)
55 | }
56 | }
57 |
58 | #[cfg(test)]
59 | mod require_with_string_tests {
60 |
61 | use crate::detect::detector::IssueDetector;
62 |
63 | use super::EmptyRequireRevertDetector;
64 |
65 | #[test]
66 | fn test_require_with_string_by_loading_contract_directly() {
67 | let context = crate::detect::test_utils::load_solidity_source_unit(
68 | "../tests/contract-playground/src/DeprecatedOZFunctions.sol",
69 | );
70 |
71 | let mut detector = EmptyRequireRevertDetector::default();
72 | let found = detector.detect(&context).unwrap();
73 | assert!(found);
74 | assert_eq!(detector.instances().len(), 2);
75 | }
76 |
77 | #[test]
78 | fn test_require_with_custom_error_by_loading_contract_directly() {
79 | let context = crate::detect::test_utils::load_solidity_source_unit(
80 | "../tests/contract-playground/src/UnusedError.sol",
81 | );
82 |
83 | let mut detector = EmptyRequireRevertDetector::default();
84 | let found = detector.detect(&context).unwrap();
85 | assert!(!found);
86 | }
87 | }
88 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/graph/traits.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::ast::{ASTNode, FunctionDefinition, ModifierDefinition};
2 |
3 | /// Trait to support reversing of callgraph. (Because, direct impl is not allowed on Foreign Types)
4 | pub trait Transpose {
5 | fn reverse(&self) -> Self;
6 | }
7 |
8 | /// Use with [`super::CallGraph`]
9 | pub trait CallGraphVisitor {
10 | /// Shift all logic to tracker otherwise, you would track state at 2 different places
11 | /// One at the tracker level, and other at the application level. Instead, we must
12 | /// contain all of the tracking logic in the tracker. Therefore, visit entry point
13 | /// is essential because the tracker can get to take a look at not just the
14 | /// inward functions and modifiers, but also the entry points that have invoked it.
15 | fn visit_entry_point(&mut self, node: &ASTNode) -> eyre::Result<()> {
16 | self.visit_any(node)
17 | }
18 |
19 | /// Meant to be invoked while traversing
20 | /// [`crate::context::workspace_context::WorkspaceContext::inward_callgraph`]
21 | fn visit_inward_function_definition(&mut self, node: &FunctionDefinition) -> eyre::Result<()> {
22 | self.visit_any(&(node.into()))
23 | }
24 |
25 | /// Meant to be invoked while traversing
26 | /// [`crate::context::workspace_context::WorkspaceContext::outward_callgraph`]
27 | fn visit_outward_function_definition(&mut self, node: &FunctionDefinition) -> eyre::Result<()> {
28 | self.visit_any(&(node.into()))
29 | }
30 |
31 | /// Meant to be invoked while traversing
32 | /// [`crate::context::workspace_context::WorkspaceContext::inward_callgraph`]
33 | fn visit_inward_modifier_definition(&mut self, node: &ModifierDefinition) -> eyre::Result<()> {
34 | self.visit_any(&(node.into()))
35 | }
36 |
37 | /// Meant to be invoked while traversing
38 | /// [`crate::context::workspace_context::WorkspaceContext::outward_callgraph`]
39 | fn visit_outward_modifier_definition(&mut self, node: &ModifierDefinition) -> eyre::Result<()> {
40 | self.visit_any(&(node.into()))
41 | }
42 |
43 | /// Read as "outward's inward-side-effect" function definition
44 | /// These are function definitions that are inward from the outward nodes
45 | /// but are themselves neither outward nor inward to the entry points
46 | fn visit_outward_side_effect_function_definition(
47 | &mut self,
48 | node: &FunctionDefinition,
49 | ) -> eyre::Result<()> {
50 | self.visit_any(&(node.into()))
51 | }
52 |
53 | /// Read as "outward's inward-side-effect" modifier definition
54 | /// These are modifier definitions that are inward from the outward nodes
55 | /// but are themselves neither outward nor inward to the entry points
56 | fn visit_outward_side_effect_modifier_definition(
57 | &mut self,
58 | node: &ModifierDefinition,
59 | ) -> eyre::Result<()> {
60 | self.visit_any(&(node.into()))
61 | }
62 |
63 | fn visit_any(&mut self, _node: &ASTNode) -> eyre::Result<()> {
64 | Ok(())
65 | }
66 | }
67 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/graph/preprocess/new.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{
2 | ast::ContractDefinition,
3 | context::{
4 | browser::{ExtractFunctionCalls, ExtractModifierInvocations},
5 | graph::*,
6 | workspace::WorkspaceContext,
7 | },
8 | };
9 | use std::collections::{HashSet, hash_map::*};
10 |
11 | impl WorkspaceCallGraphs {
12 | pub fn build(context: &WorkspaceContext) -> WorkspaceCallGraphs {
13 | let mut workspace_cg: WorkspaceCallGraphs = Default::default();
14 | for contract in context.deployable_contracts() {
15 | if let Some(raw_callgraph) = _create_raw_callgraph(context, contract) {
16 | workspace_cg.inward_callgraphs.insert(contract.id, raw_callgraph.clone());
17 | workspace_cg.outward_callgraphs.insert(contract.id, raw_callgraph.reverse());
18 | }
19 | }
20 | workspace_cg
21 | }
22 | }
23 |
24 | pub fn _create_raw_callgraph(
25 | context: &WorkspaceContext,
26 | contract: &ContractDefinition,
27 | ) -> Option<RawCallGraph> {
28 | let mut raw_callgraph = Default::default();
29 | let mut visited: HashSet<NodeID> = Default::default();
30 | for entrypoint in context.entrypoint_functions(contract)? {
31 | let mut current = vec![entrypoint.id];
32 | while let Some(node_id) = current.pop() {
33 | if visited.contains(&node_id) {
34 | continue;
35 | }
36 | visited.insert(node_id);
37 | create_node_if_not_exists(node_id, &mut raw_callgraph);
38 | let Some(node) = context.nodes.get(&node_id) else {
39 | continue;
40 | };
41 | for function_call in ExtractFunctionCalls::from(node).extracted {
42 | if let Some(f) = context.resolve_internal_call(contract, &function_call) {
43 | create_connection_if_not_exists(node_id, f.id, &mut raw_callgraph);
44 | current.push(f.id);
45 | }
46 | }
47 | for modifier_call in ExtractModifierInvocations::from(node).extracted {
48 | if let Some(m) = context.resolve_modifier_call(contract, &modifier_call) {
49 | create_connection_if_not_exists(node_id, m.id, &mut raw_callgraph);
50 | current.push(m.id);
51 | }
52 | }
53 | }
54 | }
55 | Some(raw_callgraph)
56 | }
57 |
58 | fn create_node_if_not_exists(node_id: NodeID, raw_callgraph: &mut RawCallGraph) {
59 | if let Entry::Vacant(v) = raw_callgraph.entry(node_id) {
60 | v.insert(vec![]);
61 | }
62 | }
63 |
64 | fn create_connection_if_not_exists(
65 | from_id: NodeID,
66 | to_id: NodeID,
67 | raw_callgraph: &mut RawCallGraph,
68 | ) {
69 | match raw_callgraph.entry(from_id) {
70 | Entry::Occupied(mut o) => {
71 | // Performance Tip: Maybe later use binary search (it requires keeping ascending order
72 | // while inserting tho)
73 | if !o.get().contains(&to_id) {
74 | o.get_mut().push(to_id);
75 | }
76 | }
77 | Entry::Vacant(v) => {
78 | v.insert(vec![to_id]);
79 | }
80 | }
81 | }
82 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/dynamic_array_length_assignment.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::NodeID;
4 |
5 | use crate::{
6 | capture,
7 | context::workspace::WorkspaceContext,
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct DynamicArrayLengthAssignmentDetector {
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 DynamicArrayLengthAssignmentDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | for member_access in context
22 | .member_accesses()
23 | .into_iter()
24 | .filter(|member_access| member_access.l_value_requested)
25 | {
26 | let assignment_to = member_access.expression.type_descriptions();
27 |
28 | let is_being_assigned_on_dynamic_array = assignment_to.is_some_and(|assignment_to| {
29 | assignment_to
30 | .type_string
31 | .as_ref()
32 | .is_some_and(|type_string| type_string.ends_with("[] storage ref"))
33 | });
34 |
35 | let is_being_assigned_to_length_property = member_access.member_name == "length";
36 |
37 | if is_being_assigned_on_dynamic_array && is_being_assigned_to_length_property {
38 | capture!(self, context, member_access);
39 | }
40 | }
41 |
42 | Ok(!self.found_instances.is_empty())
43 | }
44 |
45 | fn severity(&self) -> IssueSeverity {
46 | IssueSeverity::High
47 | }
48 |
49 | fn title(&self) -> String {
50 | String::from("Direct assignment of array length")
51 | }
52 |
53 | fn description(&self) -> String {
54 | String::from(
55 | "If the length of a dynamic array (storage variable) is directly assigned to, \
56 | it may allow access to other storage slots by tweaking it's value. This practice has \
57 | been deprecated in newer Solidity versions",
58 | )
59 | }
60 |
61 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
62 | self.found_instances.clone()
63 | }
64 |
65 | fn name(&self) -> String {
66 | IssueDetectorNamePool::DynamicArrayLengthAssignment.to_string()
67 | }
68 | }
69 |
70 | #[cfg(test)]
71 | mod dynamic_array_length_assignment_tests {
72 |
73 | use crate::detect::{
74 | detector::IssueDetector, high::DynamicArrayLengthAssignmentDetector,
75 | test_utils::load_solidity_source_unit,
76 | };
77 |
78 | #[test]
79 |
80 | fn test_dynamic_array_length_assignment() {
81 | let context = load_solidity_source_unit(
82 | "../tests/contract-playground/src/DynamicArrayLengthAssignment.sol",
83 | );
84 |
85 | let mut detector = DynamicArrayLengthAssignmentDetector::default();
86 | let found = detector.detect(&context).unwrap();
87 |
88 | assert!(found);
89 | assert_eq!(detector.instances().len(), 5);
90 | }
91 | }
92 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/redundant_statement.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{Expression, 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 | // HOW TO USE THIS TEMPLATE:
13 | // 1. Copy this file and rename it to the snake_case version of the issue you are detecting.
14 | // 2. Rename the RedundantStatementDetector struct and impl to your new issue name.
15 | // 3. Add this file and detector struct to the mod.rs file in the same directory.
16 | // 4. Implement the detect function to find instances of the issue.
17 |
18 | #[derive(Default)]
19 | pub struct RedundantStatementDetector {
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 RedundantStatementDetector {
26 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
27 | for expression_statement in context.expression_statements() {
28 | if let Some(parent) = expression_statement.parent(context) {
29 | if parent.node_type() != NodeType::Block {
30 | continue;
31 | }
32 |
33 | match &expression_statement.expression {
34 | Expression::Identifier(identifier) => {
35 | capture!(self, context, identifier);
36 | }
37 | Expression::ElementaryTypeNameExpression(elementary_type_expression) => {
38 | capture!(self, context, elementary_type_expression);
39 | }
40 | _ => (),
41 | };
42 | }
43 | }
44 |
45 | Ok(!self.found_instances.is_empty())
46 | }
47 |
48 | fn severity(&self) -> IssueSeverity {
49 | IssueSeverity::Low
50 | }
51 |
52 | fn title(&self) -> String {
53 | String::from("Redundant Statement")
54 | }
55 |
56 | fn description(&self) -> String {
57 | String::from("Remove the redundant statement.")
58 | }
59 |
60 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
61 | self.found_instances.clone()
62 | }
63 |
64 | fn name(&self) -> String {
65 | IssueDetectorNamePool::RedundantStatement.to_string()
66 | }
67 | }
68 |
69 | #[cfg(test)]
70 | mod redundant_statements_detector {
71 |
72 | use crate::detect::{
73 | detector::IssueDetector, low::redundant_statement::RedundantStatementDetector,
74 | };
75 |
76 | #[test]
77 |
78 | fn test_redundant_statements() {
79 | let context = crate::detect::test_utils::load_solidity_source_unit(
80 | "../tests/contract-playground/src/RedundantStatements.sol",
81 | );
82 |
83 | let mut detector = RedundantStatementDetector::default();
84 | let found = detector.detect(&context).unwrap();
85 | assert!(found);
86 | assert_eq!(detector.instances().len(), 6);
87 | }
88 | }
89 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/todo.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::NodeID,
5 | capture,
6 | context::{
7 | browser::Peek,
8 | workspace::{ASTNode, WorkspaceContext},
9 | },
10 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
11 | stats,
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct TodoDetector {
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 | hints: BTreeMap<(String, usize, String), String>,
21 | }
22 |
23 | impl IssueDetector for TodoDetector {
24 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
25 | for contract in context.contract_definitions() {
26 | let contract_as_ast: ASTNode = contract.into();
27 | if let Some(contract_code) = contract_as_ast.peek(context) {
28 | if contract_code.is_empty() {
29 | continue;
30 | }
31 | let tokens = stats::token::tokenize(&contract_code);
32 | for token in tokens {
33 | match token.token_type {
34 | stats::token::TokenType::MultilineComment
35 | | stats::token::TokenType::SinglelineComment => {
36 | if token.content.to_lowercase().contains("todo") {
37 | capture!(self, context, contract);
38 | break;
39 | }
40 | }
41 | _ => (),
42 | }
43 | }
44 | }
45 | }
46 |
47 | Ok(!(self.found_instances.is_empty()))
48 | }
49 |
50 | fn title(&self) -> String {
51 | String::from("Contract has TODO Comments")
52 | }
53 |
54 | fn description(&self) -> String {
55 | String::from(
56 | "Contract contains comments with TODOS. Consider implementing or removing them.",
57 | )
58 | }
59 |
60 | fn severity(&self) -> IssueSeverity {
61 | IssueSeverity::Low
62 | }
63 |
64 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
65 | self.found_instances.clone()
66 | }
67 |
68 | fn hints(&self) -> BTreeMap<(String, usize, String), String> {
69 | self.hints.clone()
70 | }
71 |
72 | fn name(&self) -> String {
73 | format!("{}", IssueDetectorNamePool::Todo)
74 | }
75 | }
76 |
77 | #[cfg(test)]
78 | mod contracts_with_todos_tests {
79 |
80 | use crate::detect::detector::IssueDetector;
81 |
82 | use super::TodoDetector;
83 |
84 | #[test]
85 |
86 | fn test_contracts_with_todos_by_loading_contract_directly() {
87 | let context = crate::detect::test_utils::load_solidity_source_unit(
88 | "../tests/contract-playground/src/ContractWithTodo.sol",
89 | );
90 |
91 | let mut detector = TodoDetector::default();
92 | let found = detector.detect(&context).unwrap();
93 |
94 | assert!(found);
95 | assert_eq!(detector.instances().len(), 1);
96 | }
97 | }
98 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/unchecked_send.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{ASTNode, NodeID, NodeType};
4 |
5 | use crate::{
6 | capture,
7 | context::{browser::GetImmediateParent, workspace::WorkspaceContext},
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct UncheckedSendDetector {
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 UncheckedSendDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | for member_access in context.member_accesses() {
22 | if member_access.member_name == "send"
23 | && member_access.expression.type_descriptions().is_some_and(|type_desc| {
24 | type_desc.type_string.as_ref().is_some_and(|type_string| {
25 | type_string == "address" || type_string == "address payable"
26 | })
27 | })
28 | && let Some(ASTNode::FunctionCall(func_call)) = member_access.parent(context)
29 | && let Some(ASTNode::ExpressionStatement(expr_stmnt)) = func_call.parent(context)
30 | && expr_stmnt
31 | .parent(context)
32 | .is_some_and(|node| node.node_type() == NodeType::Block)
33 | {
34 | capture!(self, context, func_call);
35 | }
36 | }
37 |
38 | Ok(!self.found_instances.is_empty())
39 | }
40 |
41 | fn severity(&self) -> IssueSeverity {
42 | IssueSeverity::High
43 | }
44 |
45 | fn title(&self) -> String {
46 | String::from("Unchecked `bool success` value for ETH send")
47 | }
48 |
49 | fn description(&self) -> String {
50 | String::from(
51 | "The call `address(payable?).send(address)` may fail because of reasons like out-of-gas, \
52 | invalid recipient address or revert from the recipient, but not revert the transaction. Therefore, the boolean returned by this function call must be checked \
53 | to be `true` in order to verify that the transaction was successful.",
54 | )
55 | }
56 |
57 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
58 | self.found_instances.clone()
59 | }
60 |
61 | fn name(&self) -> String {
62 | IssueDetectorNamePool::UncheckedSend.to_string()
63 | }
64 | }
65 |
66 | #[cfg(test)]
67 | mod unchecked_send_tests {
68 |
69 | use crate::detect::{detector::IssueDetector, high::unchecked_send::UncheckedSendDetector};
70 |
71 | #[test]
72 | fn test_unchecked_send() {
73 | let context = crate::detect::test_utils::load_solidity_source_unit(
74 | "../tests/contract-playground/src/UncheckedSend.sol",
75 | );
76 |
77 | let mut detector = UncheckedSendDetector::default();
78 | let found = detector.detect(&context).unwrap();
79 |
80 | assert!(found);
81 | assert_eq!(detector.instances().len(), 1);
82 | }
83 | }
84 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/reused_contract_name.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{
2 | collections::{BTreeMap, HashMap},
3 | error::Error,
4 | };
5 |
6 | use crate::ast::{ContractDefinition, NodeID};
7 |
8 | use crate::{
9 | capture,
10 | context::workspace::WorkspaceContext,
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct ReusedContractNameDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for ReusedContractNameDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | let mut contract_names: HashMap<&str, Vec<&ContractDefinition>> = HashMap::new();
25 |
26 | // Simplify the map filling process using the Entry API
27 | for contract in context.contract_definitions() {
28 | contract_names.entry(&contract.name).or_default().push(contract);
29 | }
30 |
31 | // Process duplicate contracts
32 | contract_names
33 | .values() // Directly iterate over values
34 | .filter(|contracts| contracts.len() > 1) // Filter for duplicates
35 | .flatten() // Flatten the list of lists to a single list of contracts
36 | .for_each(|contract| capture!(self, context, contract)); // Process each contract
37 |
38 | Ok(!self.found_instances.is_empty())
39 | }
40 |
41 | fn severity(&self) -> IssueSeverity {
42 | IssueSeverity::High
43 | }
44 |
45 | fn title(&self) -> String {
46 | String::from("Contract Name Reused in Different Files")
47 | }
48 |
49 | fn description(&self) -> String {
50 | String::from(
51 | "When compiling contracts with certain development frameworks (for example: Truffle), having contracts with the same name across different files can lead to one being overwritten.",
52 | )
53 | }
54 |
55 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
56 | self.found_instances.clone()
57 | }
58 |
59 | fn name(&self) -> String {
60 | IssueDetectorNamePool::ReusedContractName.to_string()
61 | }
62 | }
63 |
64 | #[cfg(test)]
65 | mod reused_contract_name_detector_tests {
66 | use semver::Version;
67 |
68 | use crate::detect::{
69 | detector::IssueDetector, high::ReusedContractNameDetector,
70 | test_utils::load_multiple_solidity_source_units_into_single_context,
71 | };
72 |
73 | #[test]
74 |
75 | fn test_reused_contract_name_detector() {
76 | let context = load_multiple_solidity_source_units_into_single_context(
77 | &[
78 | "../tests/contract-playground/src/reused_contract_name/ContractA.sol",
79 | "../tests/contract-playground/src/reused_contract_name/ContractB.sol",
80 | ],
81 | Version::new(0, 8, 19),
82 | );
83 |
84 | let mut detector = ReusedContractNameDetector::default();
85 | let found = detector.detect(&context).unwrap();
86 | assert!(found);
87 | assert_eq!(detector.instances().len(), 2);
88 | }
89 | }
90 |
```
--------------------------------------------------------------------------------
/aderyn_core/tests/traversal.rs:
--------------------------------------------------------------------------------
```rust
1 | mod common;
2 |
3 | use aderyn_core::detect::{detector::IssueDetector, test_utils::load_solidity_source_unit};
4 | use common::*;
5 |
6 | #[test]
7 | fn test_immediate_parent_demo() {
8 | let context = load_solidity_source_unit(
9 | "../tests/contract-playground/src/parent_chain/ParentChainContract.sol",
10 | );
11 |
12 | let mut detector = ImmediateParentDemonstrator::default();
13 | let found = detector.detect(&context).unwrap();
14 | assert!(found);
15 |
16 | println!("Total number of instances: {:?}", detector.instances().len());
17 | assert!(detector.instances().len() == 3);
18 | }
19 |
20 | #[test]
21 | fn test_immediate_child_demo() {
22 | let context = load_solidity_source_unit(
23 | "../tests/contract-playground/src/parent_chain/ParentChainContract.sol",
24 | );
25 |
26 | let mut detector = ImmediateChildrenDemonstrator::default();
27 | let found = detector.detect(&context).unwrap();
28 | assert!(found);
29 |
30 | println!("Total number of instances: {:?}", detector.instances().len());
31 | assert!(detector.instances().len() == 1);
32 | }
33 |
34 | #[test]
35 | fn test_closest_ancestor() {
36 | let context = load_solidity_source_unit(
37 | "../tests/contract-playground/src/parent_chain/ParentChainContract.sol",
38 | );
39 |
40 | let mut detector = ClosestAncestorDemonstrator::default();
41 | let found = detector.detect(&context).unwrap();
42 | assert!(found);
43 |
44 | println!("Total number of instances: {:?}", detector.instances().len());
45 | assert!(detector.instances().len() == 4);
46 | }
47 |
48 | #[test]
49 | fn test_ancestral_line_demo() {
50 | let context = load_solidity_source_unit(
51 | "../tests/contract-playground/src/parent_chain/ParentChainContract.sol",
52 | );
53 |
54 | let mut detector = AncestralLineDemonstrator::default();
55 | let found = detector.detect(&context).unwrap();
56 | assert!(found);
57 |
58 | println!("Total number of instances: {:?}", detector.instances().len());
59 | assert!(detector.instances().len() == 4);
60 | }
61 |
62 | #[test]
63 | fn test_new_ast_nodes() {
64 | let context = load_solidity_source_unit("../tests/adhoc-sol-files/DemoASTNodes.sol");
65 |
66 | let mut detector = NewASTNodesDemonstrator::default();
67 | let _ = detector.detect(&context).unwrap();
68 |
69 | let instances = detector.instances();
70 | //println!("{:?}", instances);
71 |
72 | assert!(instances.len() == 4);
73 | }
74 |
75 | #[test]
76 | fn transient_can_compile() {
77 | load_solidity_source_unit("../tests/contract-playground/src/TransientKeyword.sol");
78 | }
79 |
80 | #[test]
81 | fn test_peek_over() {
82 | let context =
83 | load_solidity_source_unit("../tests/contract-playground/src/StorageConditionals.sol");
84 | let mut detector = PeekOverDemonstrator::default();
85 | let _ = detector.detect(&context).unwrap();
86 |
87 | let instances = detector.instances();
88 | assert!(instances.len() == 2);
89 | }
90 |
91 | #[test]
92 | fn test_siblings() {
93 | let context =
94 | load_solidity_source_unit("../tests/contract-playground/src/StorageConditionals.sol");
95 |
96 | let mut detector = SiblingDemonstrator::default();
97 | let _ = detector.detect(&context).unwrap();
98 | assert_eq!(detector.instances().len(), 1);
99 | }
100 |
```
--------------------------------------------------------------------------------
/tests/ast/used_errors.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"C":[19],"X":[2],"f":[9]},"id":20,"nodeType":"SourceUnit","nodes":[{"errorSelector":"c1599bd9","id":2,"name":"X","nameLocation":"6:1:1","nodeType":"ErrorDefinition","parameters":{"id":1,"nodeType":"ParameterList","parameters":[],"src":"7:2:1"},"src":"0:10:1"},{"body":{"id":8,"nodeType":"Block","src":"29:15:1","statements":[{"errorCall":{"arguments":[],"expression":{"argumentTypes":[],"id":5,"name":"X","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":2,"src":"38:1:1","typeDescriptions":{"typeIdentifier":"t_function_error_pure$__$returns$__$","typeString":"function () pure"}},"id":6,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"38:3:1","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":7,"nodeType":"RevertStatement","src":"31:10:1"}]},"id":9,"implemented":true,"kind":"freeFunction","modifiers":[],"name":"f","nameLocation":"20:1:1","nodeType":"FunctionDefinition","parameters":{"id":3,"nodeType":"ParameterList","parameters":[],"src":"21:2:1"},"returnParameters":{"id":4,"nodeType":"ParameterList","parameters":[],"src":"29:0:1"},"scope":20,"src":"11:33:1","stateMutability":"pure","virtual":false,"visibility":"internal"},{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":19,"linearizedBaseContracts":[19],"name":"C","nameLocation":"54:1:1","nodeType":"ContractDefinition","nodes":[{"errorSelector":"2bc80f3a","id":11,"name":"T","nameLocation":"68:1:1","nodeType":"ErrorDefinition","parameters":{"id":10,"nodeType":"ParameterList","parameters":[],"src":"69:2:1"},"src":"62:10:1"},{"body":{"id":17,"nodeType":"Block","src":"97:8:1","statements":[{"expression":{"arguments":[],"expression":{"argumentTypes":[],"id":14,"name":"f","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":9,"src":"99:1:1","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$__$returns$__$","typeString":"function () pure"}},"id":15,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"99:3:1","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":16,"nodeType":"ExpressionStatement","src":"99:3:1"}]},"functionSelector":"b8c9d365","id":18,"implemented":true,"kind":"function","modifiers":[],"name":"h","nameLocation":"86:1:1","nodeType":"FunctionDefinition","parameters":{"id":12,"nodeType":"ParameterList","parameters":[],"src":"87:2:1"},"returnParameters":{"id":13,"nodeType":"ParameterList","parameters":[],"src":"97:0:1"},"scope":19,"src":"77:28:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":20,"src":"45:62:1","usedErrors":[2,11]}],"src":"0:108:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/multiple_constructors.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{FunctionKind, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::{browser::ExtractFunctionDefinitions, workspace::WorkspaceContext},
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct MultipleConstructorsDetector {
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 MultipleConstructorsDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | let contracts_with_multiple_constructors = context
22 | .contract_definitions()
23 | .into_iter()
24 | .filter(|&contract| {
25 | ExtractFunctionDefinitions::from(contract)
26 | .extracted
27 | .iter()
28 | .filter(|function| function.kind() == &FunctionKind::Constructor)
29 | .count()
30 | > 1
31 | })
32 | .collect::<Vec<_>>();
33 |
34 | for contract in contracts_with_multiple_constructors {
35 | capture!(self, context, contract);
36 | }
37 |
38 | Ok(!self.found_instances.is_empty())
39 | }
40 |
41 | fn severity(&self) -> IssueSeverity {
42 | IssueSeverity::High
43 | }
44 |
45 | fn title(&self) -> String {
46 | String::from("Contract Has Multiple Constructors")
47 | }
48 |
49 | fn description(&self) -> String {
50 | String::from(
51 | "In some versions of Solidity, contracts compile with multiple constructors. The first constructor takes precedence. This can lead to unexpected behavior.",
52 | )
53 | }
54 |
55 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
56 | self.found_instances.clone()
57 | }
58 |
59 | fn name(&self) -> String {
60 | IssueDetectorNamePool::MultipleConstructors.to_string()
61 | }
62 | }
63 |
64 | #[cfg(test)]
65 | mod multiple_constructors_detector_tests {
66 |
67 | use crate::detect::{detector::IssueDetector, high::MultipleConstructorsDetector};
68 |
69 | #[test]
70 |
71 | fn test_multiple_constructors_detector() {
72 | let context = crate::detect::test_utils::load_solidity_source_unit(
73 | "../tests/contract-playground/src/MultipleConstructorSchemes.sol",
74 | );
75 |
76 | let mut detector = MultipleConstructorsDetector::default();
77 | let found = detector.detect(&context).unwrap();
78 | assert!(found);
79 | assert_eq!(detector.instances().len(), 1);
80 | }
81 |
82 | #[test]
83 |
84 | fn test_multiple_constructors_detector_no_issue() {
85 | let context = crate::detect::test_utils::load_solidity_source_unit(
86 | "../tests/contract-playground/src/ArbitraryTransferFrom.sol",
87 | );
88 |
89 | let mut detector = MultipleConstructorsDetector::default();
90 | let found = detector.detect(&context).unwrap();
91 | assert!(!found);
92 | assert_eq!(detector.instances().len(), 0);
93 | }
94 | }
95 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/dead_code.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{ASTNode, ContractKind, FunctionKind, NodeID, NodeType, Visibility};
4 |
5 | use crate::{
6 | capture,
7 | context::{browser::GetClosestAncestorOfTypeX, workspace::WorkspaceContext},
8 | detect::{
9 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
10 | helpers,
11 | },
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct DeadCodeDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for DeadCodeDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | // Heuristic:
25 | // Internal non overriding functions inside of non abstract contracts that have a body
26 | // (implemented) and are not used If an internal function is marked override then,
27 | // it may still be used even if it doesn't have a direct referencedDeclaration
28 | // pointing to it.
29 |
30 | for func in context
31 | .function_definitions()
32 | .into_iter()
33 | .filter(|&f| {
34 | f.overrides.is_none()
35 | && f.implemented
36 | && f.visibility == Visibility::Internal
37 | && f.kind() != &FunctionKind::Constructor
38 | })
39 | .filter(|&f| {
40 | if let Some(ASTNode::ContractDefinition(contract)) =
41 | f.closest_ancestor_of_type(context, NodeType::ContractDefinition)
42 | && contract.kind == ContractKind::Contract
43 | && !contract.is_abstract
44 | {
45 | return true;
46 | }
47 | false
48 | })
49 | {
50 | if helpers::count_identifiers_that_reference_an_id(context, func.id) == 0 {
51 | capture!(self, context, func);
52 | }
53 | }
54 |
55 | Ok(!self.found_instances.is_empty())
56 | }
57 |
58 | fn severity(&self) -> IssueSeverity {
59 | IssueSeverity::Low
60 | }
61 |
62 | fn title(&self) -> String {
63 | String::from("Dead Code")
64 | }
65 |
66 | fn description(&self) -> String {
67 | String::from("Functions that are not used. Consider removing them.")
68 | }
69 |
70 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
71 | self.found_instances.clone()
72 | }
73 |
74 | fn name(&self) -> String {
75 | format!("{}", IssueDetectorNamePool::DeadCode)
76 | }
77 | }
78 |
79 | #[cfg(test)]
80 | mod dead_code_tests {
81 |
82 | use crate::detect::{detector::IssueDetector, low::dead_code::DeadCodeDetector};
83 |
84 | #[test]
85 |
86 | fn test_dead_code() {
87 | let context = crate::detect::test_utils::load_solidity_source_unit(
88 | "../tests/contract-playground/src/DeadCode.sol",
89 | );
90 |
91 | let mut detector = DeadCodeDetector::default();
92 | let found = detector.detect(&context).unwrap();
93 | assert!(found);
94 | assert_eq!(detector.instances().len(), 1);
95 | }
96 | }
97 |
```
--------------------------------------------------------------------------------
/aderyn_core/tests/common/immediate_parent.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::{AppearsAfterNodeLocation, AppearsBeforeNodeLocation, GetImmediateParent},
9 | workspace::{ASTNode, WorkspaceContext},
10 | },
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct ImmediateParentDemonstrator {
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 ImmediateParentDemonstrator {
28 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
29 | for assignment in context.assignments() {
30 | println!("0 {}", assignment);
31 | capture!(self, context, assignment);
32 | if let Some(first_parent) = assignment.parent(context)
33 | && let ASTNode::ExpressionStatement(expr_stmnt) = first_parent
34 | {
35 | println!("1 {}", expr_stmnt);
36 | if let Some(second_parent) = first_parent.parent(context)
37 | && let ASTNode::Block(for_statement) = second_parent
38 | {
39 | println!("2 {}", for_statement);
40 | capture!(self, context, second_parent);
41 | if let Some(third_parent) = for_statement.parent(context) {
42 | if let ASTNode::ForStatement(block) = third_parent {
43 | println!("3 {}", block);
44 | capture!(self, context, third_parent);
45 | }
46 |
47 | assert!(first_parent.appears_after(context, second_parent).unwrap());
48 | assert!(first_parent.appears_after(context, for_statement).unwrap());
49 | assert!(expr_stmnt.appears_after(context, for_statement).unwrap());
50 | assert!(second_parent.appears_after(context, third_parent).unwrap());
51 | assert!(second_parent.appears_before(context, first_parent).unwrap());
52 | assert!(third_parent.appears_before(context, second_parent).unwrap());
53 | }
54 | }
55 | }
56 | }
57 |
58 | Ok(!self.found_instances.is_empty())
59 | }
60 |
61 | fn severity(&self) -> IssueSeverity {
62 | IssueSeverity::High
63 | }
64 |
65 | fn title(&self) -> String {
66 | String::from("ImmediateParentDemonstrator")
67 | }
68 |
69 | fn description(&self) -> String {
70 | String::from("ImmediateParentDemonstrator")
71 | }
72 |
73 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
74 | self.found_instances.clone()
75 | }
76 |
77 | fn name(&self) -> String {
78 | format!("{}", IssueDetectorNamePool::CentralizationRisk)
79 | }
80 | }
81 |
```
--------------------------------------------------------------------------------
/tests/ast/slot_offset.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"C":[12]},"id":13,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":12,"linearizedBaseContracts":[12],"name":"C","nameLocation":"9:1:1","nodeType":"ContractDefinition","nodes":[{"canonicalName":"C.S","id":3,"members":[{"constant":false,"id":2,"mutability":"mutable","name":"x","nameLocation":"33:1:1","nodeType":"VariableDeclaration","scope":3,"src":"28:6:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":1,"name":"uint","nodeType":"ElementaryTypeName","src":"28:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"name":"S","nameLocation":"24:1:1","nodeType":"StructDefinition","scope":12,"src":"17:20:1","visibility":"public"},{"constant":false,"id":6,"mutability":"mutable","name":"s","nameLocation":"44:1:1","nodeType":"VariableDeclaration","scope":12,"src":"42:3:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_struct$_S_$3_storage","typeString":"struct C.S"},"typeName":{"id":5,"nodeType":"UserDefinedTypeName","pathNode":{"id":4,"name":"S","nameLocations":["42:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":3,"src":"42:1:1"},"referencedDeclaration":3,"src":"42:1:1","typeDescriptions":{"typeIdentifier":"t_struct$_S_$3_storage_ptr","typeString":"struct C.S"}},"visibility":"internal"},{"body":{"id":10,"nodeType":"Block","src":"76:70:1","statements":[{"AST":{"nodeType":"YulBlock","src":"95:45:1","statements":[{"nodeType":"YulVariableDeclaration","src":"97:17:1","value":{"name":"s.offset","nodeType":"YulIdentifier","src":"106:8:1"},"variables":[{"name":"x","nodeType":"YulTypedName","src":"101:1:1","type":""}]},{"nodeType":"YulVariableDeclaration","src":"115:23:1","value":{"arguments":[{"name":"s.slot","nodeType":"YulIdentifier","src":"128:6:1"},{"kind":"number","nodeType":"YulLiteral","src":"136:1:1","type":"","value":"2"}],"functionName":{"name":"mul","nodeType":"YulIdentifier","src":"124:3:1"},"nodeType":"YulFunctionCall","src":"124:14:1"},"variables":[{"name":"y","nodeType":"YulTypedName","src":"119:1:1","type":""}]}]},"evmVersion":"london","externalReferences":[{"declaration":6,"isOffset":true,"isSlot":false,"src":"106:8:1","suffix":"offset","valueSize":1},{"declaration":6,"isOffset":false,"isSlot":true,"src":"128:6:1","suffix":"slot","valueSize":1}],"id":9,"nodeType":"InlineAssembly","src":"86:54:1"}]},"functionSelector":"ffae15ba","id":11,"implemented":true,"kind":"function","modifiers":[],"name":"e","nameLocation":"60:1:1","nodeType":"FunctionDefinition","parameters":{"id":7,"nodeType":"ParameterList","parameters":[],"src":"61:2:1"},"returnParameters":{"id":8,"nodeType":"ParameterList","parameters":[],"src":"76:0:1"},"scope":12,"src":"51:95:1","stateMutability":"pure","virtual":false,"visibility":"public"}],"scope":13,"src":"0:148:1","usedErrors":[]}],"src":"0:149:1"}
2 |
```
--------------------------------------------------------------------------------
/tests/ast/do_while.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"script/SomeScript.sol","id":39709,"exportedSymbols":{"Script":[113],"ScriptBase":[74],"SomeScript":[39708],"StdChains":[877],"StdCheatsSafe":[2937],"StdStorage":[4496],"StdStyle":[7346],"StdUtils":[8073],"VmSafe":[9693],"console":[18328],"console2":[26453],"safeconsole":[39691],"stdJson":[4322],"stdMath":[4464],"stdStorageSafe":[5544]},"nodeType":"SourceUnit","src":"0:188:14","nodes":[{"id":39693,"nodeType":"PragmaDirective","src":"0:23:14","nodes":[],"literals":["solidity","0.8",".23"]},{"id":39694,"nodeType":"ImportDirective","src":"25:30:14","nodes":[],"absolutePath":"lib/forge-std/src/Script.sol","file":"forge-std/Script.sol","nameLocation":"-1:-1:-1","scope":39709,"sourceUnit":114,"symbolAliases":[],"unitAlias":""},{"id":39708,"nodeType":"ContractDefinition","src":"57:131:14","nodes":[{"id":39707,"nodeType":"FunctionDefinition","src":"93:93:14","nodes":[],"body":{"id":39706,"nodeType":"Block","src":"115:71:14","nodes":[],"statements":[{"body":{"id":39703,"nodeType":"Block","src":"128:38:14","statements":[{"assignments":[39700],"declarations":[{"constant":false,"id":39700,"mutability":"mutable","name":"a","nameLocation":"150:1:14","nodeType":"VariableDeclaration","scope":39703,"src":"142:9:14","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":39699,"name":"uint256","nodeType":"ElementaryTypeName","src":"142:7:14","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"id":39702,"initialValue":{"hexValue":"31","id":39701,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"154:1:14","typeDescriptions":{"typeIdentifier":"t_rational_1_by_1","typeString":"int_const 1"},"value":"1"},"nodeType":"VariableDeclarationStatement","src":"142:13:14"}]},"condition":{"hexValue":"66616c7365","id":39704,"isConstant":false,"isLValue":false,"isPure":true,"kind":"bool","lValueRequested":false,"nodeType":"Literal","src":"173:5:14","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"},"value":"false"},"id":39705,"nodeType":"DoWhileStatement","src":"125:55:14"}]},"functionSelector":"c0406226","implemented":true,"kind":"function","modifiers":[],"name":"run","nameLocation":"102:3:14","parameters":{"id":39697,"nodeType":"ParameterList","parameters":[],"src":"105:2:14"},"returnParameters":{"id":39698,"nodeType":"ParameterList","parameters":[],"src":"115:0:14"},"scope":39708,"stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[{"baseName":{"id":39695,"name":"Script","nameLocations":["80:6:14"],"nodeType":"IdentifierPath","referencedDeclaration":113,"src":"80:6:14"},"id":39696,"nodeType":"InheritanceSpecifier","src":"80:6:14"}],"canonicalName":"SomeScript","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[39708,113,8073,2937,877,74,62],"name":"SomeScript","nameLocation":"66:10:14","scope":39709,"usedErrors":[],"usedEvents":[]}]}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/void_constructor.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{ASTNode, FunctionKind, ModifierInvocationKind, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::workspace::WorkspaceContext,
8 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | };
10 | use eyre::Result;
11 |
12 | #[derive(Default)]
13 | pub struct VoidConstructorDetector {
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 VoidConstructorDetector {
20 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
21 | // PLAN
22 | // Gather all the invocations of base constructors
23 | // For each, inspect the contract and see if there is a constructor defined. If there isn't,
24 | // capture the invocation
25 |
26 | for modifier_invocation in context.modifier_invocations() {
27 | if modifier_invocation.kind != Some(ModifierInvocationKind::BaseConstructorSpecifier) {
28 | continue;
29 | }
30 | if let Some(reference_declaration) = match &modifier_invocation.modifier_name {
31 | crate::ast::IdentifierOrIdentifierPath::Identifier(identifier) => {
32 | identifier.referenced_declaration
33 | }
34 | crate::ast::IdentifierOrIdentifierPath::IdentifierPath(identifier_path) => {
35 | Some(identifier_path.referenced_declaration)
36 | }
37 | } && let Some(ASTNode::ContractDefinition(contract)) =
38 | context.nodes.get(&reference_declaration)
39 | && contract
40 | .function_definitions()
41 | .into_iter()
42 | .filter(|f| *f.kind() == FunctionKind::Constructor)
43 | .count()
44 | == 0
45 | {
46 | capture!(self, context, modifier_invocation);
47 | }
48 | }
49 |
50 | Ok(!self.found_instances.is_empty())
51 | }
52 |
53 | fn severity(&self) -> IssueSeverity {
54 | IssueSeverity::Low
55 | }
56 |
57 | fn title(&self) -> String {
58 | String::from("Void constructor")
59 | }
60 |
61 | fn description(&self) -> String {
62 | String::from("Call to a constructor that is not implemented.")
63 | }
64 |
65 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
66 | self.found_instances.clone()
67 | }
68 |
69 | fn name(&self) -> String {
70 | format!("{}", IssueDetectorNamePool::VoidConstructor)
71 | }
72 | }
73 |
74 | #[cfg(test)]
75 | mod template_void_constructors {
76 |
77 | use crate::detect::{detector::IssueDetector, low::void_constructor::VoidConstructorDetector};
78 |
79 | #[test]
80 |
81 | fn test_template_detector() {
82 | let context = crate::detect::test_utils::load_solidity_source_unit(
83 | "../tests/contract-playground/src/VoidConstructor.sol",
84 | );
85 |
86 | let mut detector = VoidConstructorDetector::default();
87 | let found = detector.detect(&context).unwrap();
88 |
89 | assert!(found);
90 | assert_eq!(detector.instances().len(), 1);
91 | }
92 | }
93 |
```
--------------------------------------------------------------------------------
/tools/xtask/src/reportgen.rs:
--------------------------------------------------------------------------------
```rust
1 | use xshell::{Shell, cmd};
2 |
3 | use crate::flags::Reportgen;
4 |
5 | fn run_command(args: &str, release: bool) -> anyhow::Result<()> {
6 | let sh = Shell::new()?;
7 | sh.change_dir(env!("CARGO_MANIFEST_DIR"));
8 | sh.change_dir("../../");
9 |
10 | let mut cmd = cmd!(sh, "cargo run");
11 | if release {
12 | cmd = cmd.arg("--release");
13 | }
14 | cmd = cmd.arg("--").arg("--skip-update-check");
15 | cmd.args(args.split(" ")).run()?;
16 | Ok(())
17 | }
18 |
19 | fn run_command_with_env(args: &str, key: &str, val: &str, release: bool) -> anyhow::Result<()> {
20 | let sh = Shell::new()?;
21 | sh.change_dir(env!("CARGO_MANIFEST_DIR"));
22 | sh.change_dir("../../");
23 |
24 | let mut cmd = cmd!(sh, "cargo run");
25 | cmd = cmd.env(key, val);
26 | if release {
27 | cmd = cmd.arg("--release");
28 | }
29 | cmd = cmd.arg("--").arg("--skip-update-check");
30 | cmd.args(args.split(" ")).run()?;
31 | Ok(())
32 | }
33 |
34 | pub fn reportgen(choice: Reportgen) -> anyhow::Result<()> {
35 | if choice.all && choice.parallel {
36 | let sh = Shell::new()?;
37 | sh.change_dir(env!("CARGO_MANIFEST_DIR"));
38 | sh.change_dir("../../");
39 |
40 | let cmd = cmd!(sh, "chmod +x ./cli/reportgen.sh");
41 | cmd.run()?;
42 | let cmd = cmd!(sh, "./cli/reportgen.sh");
43 | cmd.run()?;
44 |
45 | return Ok(())
46 | }
47 | if choice.cpg || choice.all {
48 | run_command(
49 | "-i src/ -x lib/ ./tests/contract-playground -o ./reports/report.md",
50 | choice.release,
51 | )?;
52 | }
53 | if choice.adhoc || choice.all {
54 | run_command(
55 | "./tests/adhoc-sol-files -o ./reports/adhoc-sol-files-report.md",
56 | choice.release,
57 | )?;
58 | }
59 | if choice.sablier || choice.all {
60 | run_command(
61 | "./tests/2024-05-Sablier -o ./reports/sablier-aderyn-toml-nested-root.md",
62 | choice.release,
63 | )?;
64 | }
65 | if choice.fnft || choice.all {
66 | run_command(
67 | "./tests/foundry-nft-f23 -i src/ -x lib/ -o ./reports/nft-report.md",
68 | choice.release,
69 | )?;
70 | }
71 | if choice.fnft_icm || choice.all {
72 | run_command("./tests/foundry-nft-f23-icm -o ./reports/nft-report-icm.md", choice.release)?;
73 | }
74 |
75 | if choice.ccip || choice.all {
76 | run_command(
77 | "tests/ccip-contracts/contracts --src src/v0.8/functions/ -x tests/,test/,mocks/ -o ./reports/ccip-functions-report.md",
78 | choice.release,
79 | )?;
80 | }
81 | if choice.cpgu || choice.all {
82 | run_command_with_env(
83 | "tests/contract-playground/ -o ./reports/uniswap_profile.md",
84 | "FOUNDRY_PROFILE",
85 | "uniswap",
86 | choice.release,
87 | )?;
88 | }
89 | if choice.prb_math || choice.all {
90 | run_command("tests/prb-math -o reports/prb-math-report.md", choice.release)?;
91 | }
92 | if choice.tg || choice.all {
93 | run_command(
94 | "tests/2024-07-templegold/protocol -o reports/templegold-report.md",
95 | choice.release,
96 | )?;
97 | }
98 | if choice.hhpg || choice.all {
99 | run_command(
100 | "tests/hardhat-js-playground -o reports/hardhat-playground-report.md",
101 | choice.release,
102 | )?;
103 | }
104 | Ok(())
105 | }
106 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect.rs:
--------------------------------------------------------------------------------
```rust
1 | pub mod detector;
2 | pub mod entrypoint;
3 | pub mod helpers;
4 | pub mod high;
5 | pub mod low;
6 |
7 | pub mod test_utils;
8 |
9 | #[macro_export]
10 | macro_rules! capture {
11 | ($self:ident, $context:ident, $item:expr_2021) => {
12 | if let Some(id) = $context.get_node_id_of_capturable(&$item.clone().into()) {
13 | $self
14 | .found_instances
15 | .insert($context.get_node_sort_key_from_capturable(&$item.clone().into()), id);
16 | } else {
17 | $self
18 | .found_instances
19 | .insert($context.get_node_sort_key_from_capturable(&$item.clone().into()), 0);
20 | }
21 | };
22 | ($self:ident, $context:ident, $item:expr_2021, $hint:tt) => {
23 | $self
24 | .hints
25 | .insert($context.get_node_sort_key_from_capturable(&$item.clone().into()), $hint);
26 | capture!($self, $context, $item);
27 | };
28 | }
29 |
30 | #[macro_export]
31 | macro_rules! issue_detector {
32 | (
33 | $detector_struct:ident;
34 |
35 | severity: $detector_severity:ident,
36 | title: $detector_title:tt,
37 | desc: $detector_desc:tt,
38 | name: $detector_name:ident,
39 |
40 | |$context: ident| $e:expr_2021
41 | ) => {
42 |
43 | #[derive(Default)]
44 | pub struct $detector_struct {
45 | found_instances: std::collections::BTreeMap<(String, usize, String), $crate::ast::NodeID>,
46 | }
47 |
48 | impl $crate::detect::detector::IssueDetector for $detector_struct {
49 |
50 | fn detect(&mut self, context: &$crate::context::workspace::WorkspaceContext) -> Result<bool, Box<dyn std::error::Error>> {
51 |
52 | let $context = context;
53 |
54 | macro_rules! grab {
55 | ($item:expr_2021) => {
56 | if let Some(id) = context.get_node_id_of_capturable(&$item.clone().into()) {
57 | self.found_instances.insert(
58 | $context.get_node_sort_key_from_capturable(&$item.clone().into()),
59 | id,
60 | );
61 | } else {
62 | self.found_instances.insert(
63 | $context.get_node_sort_key_from_capturable(&$item.clone().into()),
64 | 0,
65 | );
66 | }
67 | };
68 | }
69 |
70 | $e
71 | Ok(!self.found_instances.is_empty())
72 | }
73 |
74 | fn severity(&self) -> $crate::detect::detector::IssueSeverity {
75 | $crate::detect::detector::IssueSeverity::$detector_severity
76 | }
77 |
78 | fn title(&self) -> String {
79 | String::from($detector_title)
80 | }
81 |
82 | fn description(&self) -> String {
83 | String::from($detector_desc)
84 | }
85 |
86 | fn instances(&self) -> std::collections::BTreeMap<(String, usize, String), $crate::ast::NodeID> {
87 | self.found_instances.clone()
88 | }
89 |
90 | fn name(&self) -> String {
91 | $crate::detect::detector::IssueDetectorNamePool::$detector_name.to_string()
92 | }
93 | }
94 |
95 | };
96 | }
97 |
98 | pub use capture;
99 |
100 | pub use issue_detector;
101 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/centralization_risk.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 CentralizationRiskDetector {
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 CentralizationRiskDetector {
19 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
20 | for contract_definition in context.contract_definitions().iter() {
21 | for bc in contract_definition.base_contracts.iter() {
22 | if let Some(base_name) = bc.base_name.name()
23 | && matches!(
24 | base_name.as_str(),
25 | "Owned"
26 | | "Ownable"
27 | | "Ownable2Step"
28 | | "AccessControl"
29 | | "AccessControlCrossChain"
30 | | "AccessControlEnumerable"
31 | | "Auth"
32 | | "RolesAuthority"
33 | | "MultiRolesAuthority"
34 | )
35 | {
36 | capture!(self, context, bc);
37 | }
38 | }
39 | }
40 |
41 | for modifier_invocation in context.modifier_invocations().iter().filter(|&&mi| {
42 | mi.modifier_name.name() == "onlyOwner"
43 | || mi.modifier_name.name() == "requiresAuth"
44 | || mi.modifier_name.name().contains("onlyRole")
45 | }) {
46 | capture!(self, context, modifier_invocation);
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("Centralization Risk")
58 | }
59 |
60 | fn description(&self) -> String {
61 | String::from(
62 | "Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds.",
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::CentralizationRisk)
72 | }
73 | }
74 |
75 | #[cfg(test)]
76 | mod centralization_risk_detector_tests {
77 |
78 | use crate::detect::detector::IssueDetector;
79 |
80 | use super::CentralizationRiskDetector;
81 |
82 | #[test]
83 |
84 | fn test_centralization_risk_detector_by_loading_contract_directly() {
85 | let context = crate::detect::test_utils::load_solidity_source_unit(
86 | "../tests/contract-playground/src/AdminContract.sol",
87 | );
88 |
89 | let mut detector = CentralizationRiskDetector::default();
90 | let found = detector.detect(&context).unwrap();
91 | assert!(found);
92 | // assert that the number of instances found is 3
93 | assert_eq!(detector.instances().len(), 3);
94 | }
95 | }
96 |
```