This is page 7 of 94. Use http://codebase.md/cyfrin/aderyn?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .cargo
│ └── config.toml
├── .git-blame-ignore-revs
├── .gitattributes
├── .github
│ ├── images
│ │ ├── aderyn_logo.png
│ │ ├── poweredbycyfrinblack.png
│ │ └── poweredbycyfrinblue.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── false_positive_issue.md
│ │ └── feature_request.md
│ └── workflows
│ ├── cargo.yml
│ ├── dependencies.yml
│ ├── release.yml
│ ├── reports.yml
│ └── toml.yml
├── .gitignore
├── .gitmodules
├── .vscode
│ └── settings.json
├── aderyn
│ ├── Cargo.toml
│ ├── oranda.json
│ ├── README.md
│ ├── src
│ │ ├── birdsong.rs
│ │ ├── completions.rs
│ │ ├── lib.rs
│ │ ├── lsp.rs
│ │ ├── main.rs
│ │ ├── mcp.rs
│ │ └── panic.rs
│ └── templates
│ └── aderyn.toml
├── aderyn_core
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ ├── ast
│ │ │ ├── ast_nodes.rs
│ │ │ ├── ast.rs
│ │ │ ├── impls
│ │ │ │ ├── ctx
│ │ │ │ │ ├── utils.rs
│ │ │ │ │ └── workspace.rs
│ │ │ │ ├── ctx.rs
│ │ │ │ ├── disp
│ │ │ │ │ ├── blocks.rs
│ │ │ │ │ ├── contracts.rs
│ │ │ │ │ ├── enumerations.rs
│ │ │ │ │ ├── errors.rs
│ │ │ │ │ ├── events.rs
│ │ │ │ │ ├── expressions.rs
│ │ │ │ │ ├── functions.rs
│ │ │ │ │ ├── identifiers.rs
│ │ │ │ │ ├── literals.rs
│ │ │ │ │ ├── modifiers.rs
│ │ │ │ │ ├── statements.rs
│ │ │ │ │ ├── structures.rs
│ │ │ │ │ ├── types.rs
│ │ │ │ │ ├── user_defined_value_types.rs
│ │ │ │ │ ├── using_for_directives.rs
│ │ │ │ │ └── variables.rs
│ │ │ │ ├── disp.rs
│ │ │ │ ├── node
│ │ │ │ │ ├── blocks.rs
│ │ │ │ │ ├── contracts.rs
│ │ │ │ │ ├── documentation.rs
│ │ │ │ │ ├── enumerations.rs
│ │ │ │ │ ├── errors.rs
│ │ │ │ │ ├── events.rs
│ │ │ │ │ ├── expressions.rs
│ │ │ │ │ ├── functions.rs
│ │ │ │ │ ├── identifiers.rs
│ │ │ │ │ ├── import_directives.rs
│ │ │ │ │ ├── literals.rs
│ │ │ │ │ ├── modifiers.rs
│ │ │ │ │ ├── pragma_directives.rs
│ │ │ │ │ ├── source_units.rs
│ │ │ │ │ ├── statements.rs
│ │ │ │ │ ├── structures.rs
│ │ │ │ │ ├── types.rs
│ │ │ │ │ ├── user_defined_value_types.rs
│ │ │ │ │ ├── using_for_directives.rs
│ │ │ │ │ └── variables.rs
│ │ │ │ ├── node.rs
│ │ │ │ ├── own
│ │ │ │ │ ├── hashing.rs
│ │ │ │ │ ├── node_id.rs
│ │ │ │ │ ├── source_units.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ └── own.rs
│ │ │ ├── impls.rs
│ │ │ ├── macros.rs
│ │ │ ├── magic.rs
│ │ │ ├── node_type.rs
│ │ │ └── yul.rs
│ │ ├── ast.rs
│ │ ├── audit
│ │ │ ├── attack_surface.rs
│ │ │ ├── auditor.rs
│ │ │ ├── entrypoint.rs
│ │ │ └── public_functions_no_sender.rs
│ │ ├── audit.rs
│ │ ├── context
│ │ │ ├── browser
│ │ │ │ ├── ancestral_line.rs
│ │ │ │ ├── closest_ancestor.rs
│ │ │ │ ├── external_calls.rs
│ │ │ │ ├── extractor.rs
│ │ │ │ ├── immediate_children.rs
│ │ │ │ ├── location.rs
│ │ │ │ ├── macros.rs
│ │ │ │ ├── parent.rs
│ │ │ │ ├── peek_over.rs
│ │ │ │ ├── peek_under.rs
│ │ │ │ ├── peek.rs
│ │ │ │ ├── siblings.rs
│ │ │ │ ├── sort_nodes.rs
│ │ │ │ └── storage_vars.rs
│ │ │ ├── browser.rs
│ │ │ ├── capturable.rs
│ │ │ ├── flow
│ │ │ │ ├── display.rs
│ │ │ │ ├── error.rs
│ │ │ │ ├── kind.rs
│ │ │ │ ├── primitives.rs
│ │ │ │ ├── reducibles.rs
│ │ │ │ ├── tests.rs
│ │ │ │ ├── utils.rs
│ │ │ │ ├── visualizer.rs
│ │ │ │ └── voids.rs
│ │ │ ├── flow.rs
│ │ │ ├── graph
│ │ │ │ ├── callgraph
│ │ │ │ │ ├── legacy.rs
│ │ │ │ │ ├── new.rs
│ │ │ │ │ ├── tests.rs
│ │ │ │ │ ├── utils.rs
│ │ │ │ │ └── visit.rs
│ │ │ │ ├── callgraph.rs
│ │ │ │ ├── preprocess
│ │ │ │ │ ├── legacy.rs
│ │ │ │ │ └── new.rs
│ │ │ │ ├── preprocess.rs
│ │ │ │ ├── traits.rs
│ │ │ │ └── utils.rs
│ │ │ ├── graph.rs
│ │ │ ├── macros.rs
│ │ │ ├── mcp
│ │ │ │ ├── callgraph
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── callgraph.rs
│ │ │ │ ├── contract_surface
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── util.rs
│ │ │ │ ├── contract_surface.rs
│ │ │ │ ├── list_contracts
│ │ │ │ │ ├── render.rs
│ │ │ │ │ └── tool.rs
│ │ │ │ ├── list_contracts.rs
│ │ │ │ ├── node_finder
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── node_finder.rs
│ │ │ │ ├── node_summarizer
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── node_summarizer.rs
│ │ │ │ ├── project_overview
│ │ │ │ │ ├── render.rs
│ │ │ │ │ └── tool.rs
│ │ │ │ ├── project_overview.rs
│ │ │ │ ├── tool_guide
│ │ │ │ │ └── tool.rs
│ │ │ │ └── tool_guide.rs
│ │ │ ├── mcp.rs
│ │ │ ├── router
│ │ │ │ ├── external_calls.rs
│ │ │ │ ├── internal_calls.rs
│ │ │ │ ├── modifier_calls.rs
│ │ │ │ └── tests.rs
│ │ │ ├── router.rs
│ │ │ └── workspace.rs
│ │ ├── context.rs
│ │ ├── detect
│ │ │ ├── detector.rs
│ │ │ ├── entrypoint.rs
│ │ │ ├── helpers.rs
│ │ │ ├── high
│ │ │ │ ├── _template.rs
│ │ │ │ ├── abi_encode_packed_hash_collision.rs
│ │ │ │ ├── arbitrary_transfer_from.rs
│ │ │ │ ├── const_func_changes_state.rs
│ │ │ │ ├── contract_locks_ether.rs
│ │ │ │ ├── dangerous_unary_operator.rs
│ │ │ │ ├── delegate_call_unchecked_address.rs
│ │ │ │ ├── delete_nested_mapping.rs
│ │ │ │ ├── dynamic_array_length_assignment.rs
│ │ │ │ ├── enumerable_loop_removal.rs
│ │ │ │ ├── eth_send_unchecked_address.rs
│ │ │ │ ├── experimental_encoder.rs
│ │ │ │ ├── function_selector_collision.rs
│ │ │ │ ├── incorrect_caret_operator.rs
│ │ │ │ ├── incorrect_erc20_interface.rs
│ │ │ │ ├── incorrect_erc721_interface.rs
│ │ │ │ ├── incorrect_shift_order.rs
│ │ │ │ ├── misused_boolean.rs
│ │ │ │ ├── msg_value_in_loops.rs
│ │ │ │ ├── multiple_constructors.rs
│ │ │ │ ├── nested_struct_in_mapping.rs
│ │ │ │ ├── out_of_order_retryable.rs
│ │ │ │ ├── pre_declared_variable_usage.rs
│ │ │ │ ├── reentrancy_state_change.rs
│ │ │ │ ├── reused_contract_name.rs
│ │ │ │ ├── rtlo.rs
│ │ │ │ ├── selfdestruct.rs
│ │ │ │ ├── signed_integer_storage_array.rs
│ │ │ │ ├── state_variable_shadowing.rs
│ │ │ │ ├── storage_array_memory_edit.rs
│ │ │ │ ├── strict_equality_contract_balance.rs
│ │ │ │ ├── tautological_compare.rs
│ │ │ │ ├── tautology_or_contradiction.rs
│ │ │ │ ├── tx_origin_used_for_auth.rs
│ │ │ │ ├── unchecked_low_level_call.rs
│ │ │ │ ├── unchecked_send.rs
│ │ │ │ ├── unprotected_initializer.rs
│ │ │ │ ├── unsafe_casting.rs
│ │ │ │ ├── weak_randomness.rs
│ │ │ │ └── yul_return.rs
│ │ │ ├── high.rs
│ │ │ ├── low
│ │ │ │ ├── _template.rs
│ │ │ │ ├── assert_state_change.rs
│ │ │ │ ├── block_timestamp_deadline.rs
│ │ │ │ ├── boolean_equality.rs
│ │ │ │ ├── builtin_symbol_shadowing.rs
│ │ │ │ ├── centralization_risk.rs
│ │ │ │ ├── constant_function_contains_assembly.rs
│ │ │ │ ├── costly_loop.rs
│ │ │ │ ├── dead_code.rs
│ │ │ │ ├── delegatecall_in_loop.rs
│ │ │ │ ├── deprecated_oz_function.rs
│ │ │ │ ├── division_before_multiplication.rs
│ │ │ │ ├── ecrecover.rs
│ │ │ │ ├── empty_block.rs
│ │ │ │ ├── empty_require_revert.rs
│ │ │ │ ├── function_initializing_state.rs
│ │ │ │ ├── function_pointer_in_constructor.rs
│ │ │ │ ├── inconsistent_type_names.rs
│ │ │ │ ├── incorrect_modifier.rs
│ │ │ │ ├── internal_function_used_once.rs
│ │ │ │ ├── large_numeric_literal.rs
│ │ │ │ ├── literal_instead_of_constant.rs
│ │ │ │ ├── local_variable_shadowing.rs
│ │ │ │ ├── missing_inheritance.rs
│ │ │ │ ├── modifier_used_only_once.rs
│ │ │ │ ├── multiple_placeholders.rs
│ │ │ │ ├── non_reentrant_not_first.rs
│ │ │ │ ├── push_0_opcode.rs
│ │ │ │ ├── redundant_statement.rs
│ │ │ │ ├── require_revert_in_loop.rs
│ │ │ │ ├── return_bomb.rs
│ │ │ │ ├── solmate_safe_transfer_lib.rs
│ │ │ │ ├── state_change_without_event.rs
│ │ │ │ ├── state_no_address_check.rs
│ │ │ │ ├── state_variable_could_be_constant.rs
│ │ │ │ ├── state_variable_could_be_immutable.rs
│ │ │ │ ├── state_variable_read_external.rs
│ │ │ │ ├── storage_array_length_not_cached.rs
│ │ │ │ ├── todo.rs
│ │ │ │ ├── unchecked_return.rs
│ │ │ │ ├── uninitialized_local_variable.rs
│ │ │ │ ├── unsafe_erc20_operation.rs
│ │ │ │ ├── unsafe_oz_erc721_mint.rs
│ │ │ │ ├── unspecific_solidity_pragma.rs
│ │ │ │ ├── unused_error.rs
│ │ │ │ ├── unused_import.rs
│ │ │ │ ├── unused_public_function.rs
│ │ │ │ ├── unused_state_variable.rs
│ │ │ │ └── void_constructor.rs
│ │ │ ├── low.rs
│ │ │ └── test_utils.rs
│ │ ├── detect.rs
│ │ ├── lib.rs
│ │ ├── stats
│ │ │ ├── cloc.rs
│ │ │ ├── dbg_tips.txt
│ │ │ ├── ignore.rs
│ │ │ ├── token.rs
│ │ │ └── util.rs
│ │ ├── stats.rs
│ │ ├── test_utils
│ │ │ └── load_source_unit.rs
│ │ ├── test_utils.rs
│ │ ├── visitor
│ │ │ ├── ast_visitor.rs
│ │ │ ├── macros.rs
│ │ │ └── workspace_visitor.rs
│ │ └── visitor.rs
│ ├── templates
│ │ └── mcp-tool-response
│ │ ├── callgraph.md
│ │ ├── contract_surface.md
│ │ ├── list_contracts.md
│ │ ├── node_finder_get_all.md
│ │ ├── node_finder_grep.md
│ │ ├── node_finder_search.md
│ │ ├── node_summarizer.md
│ │ ├── project_overview.md
│ │ └── tool_guide.md
│ └── tests
│ ├── common
│ │ ├── ancestral_line.rs
│ │ ├── closest_ancestor.rs
│ │ ├── immediate_children.rs
│ │ ├── immediate_parent.rs
│ │ ├── mod.rs
│ │ ├── new_ast_nodes.rs
│ │ ├── peek_over.rs
│ │ └── sibling.rs
│ └── traversal.rs
├── aderyn_driver
│ ├── .gitignore
│ ├── benches
│ │ └── detectors.rs
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ ├── compile.rs
│ │ ├── config.rs
│ │ ├── display.rs
│ │ ├── driver.rs
│ │ ├── interface
│ │ │ ├── json.rs
│ │ │ ├── lsp.rs
│ │ │ ├── markdown.rs
│ │ │ ├── mod.rs
│ │ │ ├── sarif.rs
│ │ │ ├── tables.rs
│ │ │ └── util.rs
│ │ ├── lib.rs
│ │ ├── mcp.rs
│ │ ├── process.rs
│ │ └── runner.rs
│ └── tests
│ └── astgen.rs
├── bacon.toml
├── benchmarks
│ ├── aderyn
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── iteration_times.svg
│ │ │ └── pdf.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── iteration_times_small.svg
│ │ ├── iteration_times.svg
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── relative_iteration_times_small.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── SD.svg
│ │ └── typical.svg
│ ├── arbitrary-transfer-from
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── avoid-abi-encode-packed
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── block-timestamp-deadline
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── centralization-risk
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── constants-instead-of-literals
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── delegate-call-in-loop
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── deprecated-oz-functions
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── ecrecover
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── empty-block
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── hello_world
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── inconsistent-type-names
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── large-numeric-literal
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── non-reentrant-before-others
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── push-zero-opcode
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── report
│ │ └── index.html
│ ├── require-with-string
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── solmate-safe-transfer-lib
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unindexed-events
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unprotected-initializer
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unsafe-erc20-functions
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unsafe-oz-erc721-mint
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unspecific-solidity-pragma
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-internal-function
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-modifier
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-public-function
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ └── zero-address-check
│ ├── base
│ │ ├── benchmark.json
│ │ ├── estimates.json
│ │ ├── sample.json
│ │ └── tukey.json
│ ├── change
│ │ └── estimates.json
│ ├── new
│ │ ├── benchmark.json
│ │ ├── estimates.json
│ │ ├── sample.json
│ │ └── tukey.json
│ └── report
│ ├── both
│ │ ├── pdf.svg
│ │ └── regression.svg
│ ├── change
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ └── t-test.svg
│ ├── index.html
│ ├── MAD.svg
│ ├── mean.svg
│ ├── median.svg
│ ├── pdf_small.svg
│ ├── pdf.svg
│ ├── regression_small.svg
│ ├── regression.svg
│ ├── relative_pdf_small.svg
│ ├── relative_regression_small.svg
│ ├── SD.svg
│ ├── slope.svg
│ └── typical.svg
├── Cargo.lock
├── Cargo.toml
├── cli
│ ├── benchmarks.sh
│ └── reportgen.sh
├── CODEOWNERS
├── CONTRIBUTING.md
├── cyfrinup
│ ├── dynamic_script
│ └── why.md
├── deny.toml
├── dist-workspace.toml
├── funding.json
├── LICENSE
├── Makefile
├── package-lock.json
├── package.json
├── README.md
├── RELEASE_CHECKLIST.md
├── reports
│ ├── adhoc-sol-files-highs-only-report.json
│ ├── adhoc-sol-files-report.md
│ ├── ccip-functions-report.md
│ ├── empty_report.md
│ ├── hardhat-playground-report.md
│ ├── nft-report-icm.md
│ ├── nft-report.md
│ ├── prb-math-report.md
│ ├── report.json
│ ├── report.md
│ ├── report.sarif
│ ├── sablier-aderyn-toml-nested-root.md
│ ├── templegold-report.md
│ └── uniswap_profile.md
├── rust-toolchain.toml
├── rustfmt.toml
├── tests
│ ├── adhoc-sol-files
│ │ ├── aderyn.toml
│ │ ├── Counter.sol
│ │ ├── DemoASTNodes.sol
│ │ ├── Helper.sol
│ │ ├── InconsistentUints.sol
│ │ ├── inheritance
│ │ │ ├── ExtendedInheritance.sol
│ │ │ ├── IContractInheritance.sol
│ │ │ └── InheritanceBase.sol
│ │ ├── InternalFunctions.sol
│ │ ├── lib
│ │ │ └── ThisShouldBeExcluded.sol
│ │ ├── multiple-versions
│ │ │ ├── 0.4
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.5
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.6
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.7
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ └── 0.8
│ │ │ ├── A.sol
│ │ │ └── B.sol
│ │ ├── OnceModifierExample.sol
│ │ └── StateVariables.sol
│ ├── ast
│ │ ├── abstract_contract.json
│ │ ├── address_payable.json
│ │ ├── array_type_name.json
│ │ ├── ast-erc4626.json
│ │ ├── base_constructor_call.json
│ │ ├── bit_not.json
│ │ ├── call.json
│ │ ├── constructor.json
│ │ ├── contract_dep_order.json
│ │ ├── do_while.json
│ │ ├── documentation_1.json
│ │ ├── documentation_2.json
│ │ ├── documentation_3.json
│ │ ├── documentation_local_variable.json
│ │ ├── documentation_on_statements.json
│ │ ├── documentation_triple.json
│ │ ├── empty_block.json
│ │ ├── enum_value_declaration.json
│ │ ├── enum_value.json
│ │ ├── event_definition.json
│ │ ├── experimental_encoder_pragma.json
│ │ ├── fallback_and_reveice_ether.json
│ │ ├── fallback_payable.json
│ │ ├── fallback.json
│ │ ├── function_type.json
│ │ ├── function.json
│ │ ├── global_enum.json
│ │ ├── global_struct.json
│ │ ├── inheritance_specifier.json
│ │ ├── leave.json
│ │ ├── license.json
│ │ ├── long_type_name_binary_operation.json
│ │ ├── long_type_name_identifier.json
│ │ ├── loop.json
│ │ ├── mappings.json
│ │ ├── modifier_definition.json
│ │ ├── modifier_invocation.json
│ │ ├── mutability.json
│ │ ├── nested_functions.json
│ │ ├── non_utf8.json
│ │ ├── override.json
│ │ ├── placeholder_statement.json
│ │ ├── receive_ether.json
│ │ ├── short_type_name_ref.json
│ │ ├── short_type_name.json
│ │ ├── slot_offset.json
│ │ ├── smoke.json
│ │ ├── source_location.json
│ │ ├── string.json
│ │ ├── stringlit.json
│ │ ├── switch_default.json
│ │ ├── switch.json
│ │ ├── try_catch.json
│ │ ├── two_base_functions.json
│ │ ├── unicode.json
│ │ ├── used_errors.json
│ │ ├── userDefinedValueType.json
│ │ ├── using_for_directive.json
│ │ ├── var_access.json
│ │ └── yul_hex_literal.json
│ ├── contract-playground
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── dot
│ │ │ └── .gitkeep
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── script
│ │ │ └── Counter.s.sol
│ │ ├── src
│ │ │ ├── AbstractContract.sol
│ │ │ ├── AderynIgnoreCustomDetectors.sol
│ │ │ ├── AdminContract.sol
│ │ │ ├── ArbitraryTransferFrom.sol
│ │ │ ├── AssemblyExample.sol
│ │ │ ├── AssertStateChange.sol
│ │ │ ├── auditor_mode
│ │ │ │ ├── ExternalCalls.sol
│ │ │ │ └── PublicFunctionsWithoutSenderCheck.sol
│ │ │ ├── BooleanEquality.sol
│ │ │ ├── BuiltinSymbolShadow.sol
│ │ │ ├── CacheArrayLength.sol
│ │ │ ├── CallGraphTests.sol
│ │ │ ├── Casting.sol
│ │ │ ├── cloc
│ │ │ │ ├── AnotherHeavilyCommentedContract.sol
│ │ │ │ ├── EmptyContractFile.sol
│ │ │ │ └── HeavilyCommentedContract.sol
│ │ │ ├── CompilerBugStorageSignedIntegerArray.sol
│ │ │ ├── ConstantFuncsAssembly.sol
│ │ │ ├── ConstantsLiterals.sol
│ │ │ ├── ConstFuncChangeState.sol
│ │ │ ├── ContractLocksEther.sol
│ │ │ ├── ContractWithTodo.sol
│ │ │ ├── control_flow
│ │ │ │ └── SimpleProgram.sol
│ │ │ ├── CostlyOperationsInsideLoops.sol
│ │ │ ├── Counter.sol
│ │ │ ├── CrazyPragma.sol
│ │ │ ├── DangerousStrictEquality1.sol
│ │ │ ├── DangerousStrictEquality2.sol
│ │ │ ├── DangerousUnaryOperator.sol
│ │ │ ├── DeadCode.sol
│ │ │ ├── DelegateCallWithoutAddressCheck.sol
│ │ │ ├── DeletionNestedMappingStructureContract.sol
│ │ │ ├── DeprecatedOZFunctions.sol
│ │ │ ├── DivisionBeforeMultiplication.sol
│ │ │ ├── DynamicArrayLengthAssignment.sol
│ │ │ ├── EmitAfterExternalCall.sol
│ │ │ ├── EmptyBlocks.sol
│ │ │ ├── EnumerableSetIteration.sol
│ │ │ ├── eth2
│ │ │ │ └── DepositContract.sol
│ │ │ ├── ExperimentalEncoder.sol
│ │ │ ├── ExternalCalls.sol
│ │ │ ├── FunctionInitializingState.sol
│ │ │ ├── FunctionPointers.sol
│ │ │ ├── FunctionSignatureCollision.sol
│ │ │ ├── HugeConstants.sol
│ │ │ ├── IgnoreEverything.sol
│ │ │ ├── InconsistentUints.sol
│ │ │ ├── IncorrectCaretOperator.sol
│ │ │ ├── IncorrectERC20.sol
│ │ │ ├── IncorrectERC721.sol
│ │ │ ├── IncorrectModifier.sol
│ │ │ ├── IncorrectShift.sol
│ │ │ ├── inheritance
│ │ │ │ ├── ExtendedInheritance.sol
│ │ │ │ ├── IContractInheritance.sol
│ │ │ │ └── InheritanceBase.sol
│ │ │ ├── InternalFunctions.sol
│ │ │ ├── KeccakContract.sol
│ │ │ ├── LocalVariableShadow.sol
│ │ │ ├── MissingInheritance.sol
│ │ │ ├── MisusedBoolean.sol
│ │ │ ├── MsgValueInLoop.sol
│ │ │ ├── MultipleConstructorSchemes.sol
│ │ │ ├── MultiplePlaceholders.sol
│ │ │ ├── nested
│ │ │ │ ├── 1
│ │ │ │ │ └── Nested.sol
│ │ │ │ └── 2
│ │ │ │ └── Nested.sol
│ │ │ ├── nested_mappings
│ │ │ │ ├── LaterVersion.sol
│ │ │ │ └── NestedMappings.sol
│ │ │ ├── OnceModifierExample.sol
│ │ │ ├── OnlyLibrary.sol
│ │ │ ├── OutOfOrderRetryable.sol
│ │ │ ├── parent_chain
│ │ │ │ └── ParentChainContract.sol
│ │ │ ├── PragmaRange.sol
│ │ │ ├── PreDeclaredVarUsage.sol
│ │ │ ├── PublicFunction.sol
│ │ │ ├── PublicVariableReadInExternalContext.sol
│ │ │ ├── RedundantStatements.sol
│ │ │ ├── ReturnBomb.sol
│ │ │ ├── reused_contract_name
│ │ │ │ ├── ContractA.sol
│ │ │ │ └── ContractB.sol
│ │ │ ├── RevertsAndRequriesInLoops.sol
│ │ │ ├── router
│ │ │ │ ├── ExternalCalls.sol
│ │ │ │ ├── FallbackAndReceiveOverrides.sol
│ │ │ │ ├── InternalCalls.sol
│ │ │ │ ├── ModifierCalls.sol
│ │ │ │ └── VarOverridesFunction.sol
│ │ │ ├── RTLO.sol
│ │ │ ├── SendEtherNoChecks.sol
│ │ │ ├── SendEtherNoChecksLibImport.sol
│ │ │ ├── StateChangeAfterExternalCall.sol
│ │ │ ├── StateShadowing.sol
│ │ │ ├── StateVariableCouldBeDeclaredConstant.sol
│ │ │ ├── StateVariableCouldBeDeclaredImmutable.sol
│ │ │ ├── StateVariables.sol
│ │ │ ├── StateVariablesChangesWithoutEvents.sol
│ │ │ ├── StateVariablesManipulation.sol
│ │ │ ├── StorageConditionals.sol
│ │ │ ├── StorageParameters.sol
│ │ │ ├── T11sTranferer.sol
│ │ │ ├── TautologicalCompare.sol
│ │ │ ├── TautologyOrContradiction.sol
│ │ │ ├── TestERC20.sol
│ │ │ ├── TransientKeyword.sol
│ │ │ ├── Trump.sol
│ │ │ ├── TxOriginUsedForAuth.sol
│ │ │ ├── U2.sol
│ │ │ ├── U3.sol
│ │ │ ├── U4.sol
│ │ │ ├── U5.sol
│ │ │ ├── UncheckedCalls.sol
│ │ │ ├── UncheckedReturn.sol
│ │ │ ├── UncheckedSend.sol
│ │ │ ├── UninitializedLocalVariables.sol
│ │ │ ├── UninitializedStateVariable.sol
│ │ │ ├── uniswap
│ │ │ │ ├── UniswapV2Swapper.sol
│ │ │ │ └── UniswapV3Swapper.sol
│ │ │ ├── UnprotectedInitialize.sol
│ │ │ ├── UnsafeERC721Mint.sol
│ │ │ ├── UnusedError.sol
│ │ │ ├── UnusedImport.sol
│ │ │ ├── UnusedStateVariables.sol
│ │ │ ├── UsingSelfdestruct.sol
│ │ │ ├── VoidConstructor.sol
│ │ │ ├── WeakRandomness.sol
│ │ │ ├── WrongOrderOfLayout.sol
│ │ │ ├── YulReturn.sol
│ │ │ └── ZeroAddressCheck.sol
│ │ └── test
│ │ └── Counter.t.sol
│ ├── foundry-nft-f23
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── foundry.lock
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── remappings.txt
│ │ └── src
│ │ ├── BasicNft.sol
│ │ ├── F1.sol
│ │ ├── F2.sol
│ │ ├── Initializer.sol
│ │ └── inner-core-modules
│ │ └── ICM.sol
│ ├── foundry-nft-f23-icm
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── aderyn.toml
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── remappings.txt
│ │ └── src
│ │ ├── BasicNft.sol
│ │ ├── F1.sol
│ │ ├── F2.sol
│ │ ├── Initializer.sol
│ │ └── inner-core-modules
│ │ └── ICM.sol
│ ├── hardhat-js-playground
│ │ ├── .gitignore
│ │ ├── artifacts
│ │ │ ├── build-info
│ │ │ │ └── cee6fe9a9a2f03f7ff10a27ab2746af6.json
│ │ │ └── contracts
│ │ │ ├── Counter.sol
│ │ │ │ ├── Counter.dbg.json
│ │ │ │ └── Counter.json
│ │ │ ├── ExtendedInheritance.sol
│ │ │ │ ├── ExtendedInheritance.dbg.json
│ │ │ │ └── ExtendedInheritance.json
│ │ │ ├── IContractInheritance.sol
│ │ │ │ ├── IContractInheritance.dbg.json
│ │ │ │ └── IContractInheritance.json
│ │ │ ├── InheritanceBase.sol
│ │ │ │ ├── InheritanceBase.dbg.json
│ │ │ │ └── InheritanceBase.json
│ │ │ ├── KeccakContract.sol
│ │ │ │ ├── KeccakContract.dbg.json
│ │ │ │ └── KeccakContract.json
│ │ │ ├── Lock.sol
│ │ │ │ ├── Lock.dbg.json
│ │ │ │ └── Lock.json
│ │ │ └── StateVariables.sol
│ │ │ ├── StateVariables.dbg.json
│ │ │ └── StateVariables.json
│ │ ├── contracts
│ │ │ ├── Counter.sol
│ │ │ ├── ExtendedInheritance.sol
│ │ │ ├── IContractInheritance.sol
│ │ │ ├── InheritanceBase.sol
│ │ │ ├── KeccakContract.sol
│ │ │ ├── Lock.sol
│ │ │ └── StateVariables.sol
│ │ ├── hardhat.config.js
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── scripts
│ │ │ └── deploy.js
│ │ ├── test
│ │ │ └── Lock.js
│ │ └── yarn.lock
│ ├── no-sol-files
│ │ ├── extra
│ │ │ └── HelloAgain.md
│ │ ├── Hello.txt
│ │ └── Hello.yul
│ └── toml
│ ├── nested_project1
│ │ ├── aderyn.toml
│ │ ├── folder1
│ │ │ └── hardhat.config.ts
│ │ ├── folder2
│ │ │ └── hardhat.config.ts
│ │ └── folder3
│ │ └── file.txt
│ └── nested_project2
│ ├── aderyn.toml
│ ├── folder1
│ │ └── foundry.toml
│ └── folder2
│ └── file1.txt
├── tools
│ └── xtask
│ ├── Cargo.toml
│ └── src
│ ├── blesspr.rs
│ ├── cut_release.rs
│ ├── flags.rs
│ ├── main.rs
│ ├── reportgen.rs
│ └── tomlgen.rs
└── typos.toml
```
# Files
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/require_revert_in_loop.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::NodeID;
use crate::{
capture,
context::{
browser::{ExtractIdentifiers, ExtractRevertStatements},
graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
workspace::WorkspaceContext,
},
detect::{
detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
helpers::get_explore_centers_of_loops,
},
};
use eyre::Result;
#[derive(Default)]
pub struct RequireRevertInLoopDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for RequireRevertInLoopDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
let loop_explore_centers = get_explore_centers_of_loops(context);
for l in loop_explore_centers {
let callgraphs = CallGraphConsumer::get(context, &[l], CallGraphDirection::Inward)?;
for callgraph in callgraphs {
let mut tracker = RevertAndRequireTracker::default();
callgraph.accept(context, &mut tracker)?;
if tracker.has_require_or_revert || tracker.has_revert_statement {
capture!(self, context, l);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("Loop Contains `require`/`revert`")
}
fn description(&self) -> String {
String::from(
"Avoid `require` / `revert` statements in a loop because a single bad item can cause the whole transaction to fail. It's better to forgive on fail and return failed elements post processing of the loop",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::RequireRevertInLoop)
}
}
#[derive(Default)]
struct RevertAndRequireTracker {
has_require_or_revert: bool,
has_revert_statement: bool,
}
impl CallGraphVisitor for RevertAndRequireTracker {
fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
// Check for revert() and require() calls
let identifiers = ExtractIdentifiers::from(node).extracted;
let requires_and_reverts_are_present =
identifiers.iter().any(|id| id.name == "revert" || id.name == "require");
if requires_and_reverts_are_present {
self.has_require_or_revert = true;
}
// Check for revert statements
let revert_statements = ExtractRevertStatements::from(node).extracted;
if !revert_statements.is_empty() {
self.has_revert_statement = true;
}
Ok(())
}
}
#[cfg(test)]
mod reevrts_and_requires_in_loops {
use crate::detect::{
detector::IssueDetector, low::require_revert_in_loop::RequireRevertInLoopDetector,
};
#[test]
fn test_reverts_and_requires_in_loops_by_loading_contract_directly() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/RevertsAndRequriesInLoops.sol",
);
let mut detector = RequireRevertInLoopDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 2);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/builtin_symbol_shadowing.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::NodeID;
use crate::{capture, detect::detector::IssueDetectorNamePool};
use phf::phf_set;
use crate::{
context::workspace::WorkspaceContext,
detect::detector::{IssueDetector, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct BuiltinSymbolShadowingDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for BuiltinSymbolShadowingDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// Variable Declaration names
for variable_declaration in context.variable_declarations() {
if DENY_LIST.contains(&variable_declaration.name) {
capture!(self, context, variable_declaration);
}
}
// Function Definition names
for function in context.function_definitions() {
if DENY_LIST.contains(&function.name) {
capture!(self, context, function);
}
}
// Modifier definition names
for modifier in context.modifier_definitions() {
if DENY_LIST.contains(&modifier.name) {
capture!(self, context, modifier);
}
}
// Event definition names
for event in context.event_definitions() {
if DENY_LIST.contains(&event.name) {
capture!(self, context, event);
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("Builtin Symbol Shadowing")
}
fn description(&self) -> String {
String::from("Name clashes with a built-in-symbol. Consider renaming it.")
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::BuiltinSymbolShadowing)
}
}
// Copied from SLITHER
static DENY_LIST: phf::Set<&'static str> = phf_set! {
"assert",
"require",
"revert",
"block",
"blockhash",
"gasleft",
"msg",
"now",
"tx",
"this",
"addmod",
"mulmod",
"keccak256",
"sha256",
"sha3",
"ripemd160",
"ecrecover",
"selfdestruct",
"suicide",
"abi",
"fallback",
"receive",
"abstract",
"after",
"alias",
"apply",
"auto",
"case",
"catch",
"copyof",
"default",
"define",
"final",
"immutable",
"implements",
"in",
"inline",
"let",
"macro",
"match",
"mutable",
"null",
"of",
"override",
"partial",
"promise",
"reference",
"relocatable",
"sealed",
"sizeof",
"static",
"supports",
"switch",
"try",
"type",
"typedef",
"typeof",
"unchecked",
};
#[cfg(test)]
mod builtin_symbol_shadowing_tests {
use crate::detect::{
detector::IssueDetector, low::builtin_symbol_shadowing::BuiltinSymbolShadowingDetector,
};
#[test]
fn test_builtin_symbol_shadow() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/BuiltinSymbolShadow.sol",
);
let mut detector = BuiltinSymbolShadowingDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 4);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/state_change_without_event.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::{FunctionKind, NodeID};
use crate::{
capture,
context::{
browser::ExtractEmitStatements,
graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
workspace::WorkspaceContext,
},
detect::{
detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
helpers,
},
};
use eyre::Result;
#[derive(Default)]
pub struct StateVariableChangesWithoutEventDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for StateVariableChangesWithoutEventDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for func in helpers::get_implemented_external_and_public_functions(context) {
if *func.kind() == FunctionKind::Constructor {
continue;
}
let callgraphs =
CallGraphConsumer::get(context, &[&(func.into())], CallGraphDirection::Inward)?;
for callgraph in callgraphs {
let mut tracker = EventEmissionTracker { does_emit_events: false };
callgraph.accept(context, &mut tracker)?;
if tracker.does_emit_events {
continue;
}
// At this point, we know that no events are emitted
if let Some(changes) = func.state_variable_changes(context)
&& changes.state_variables_have_been_manipulated()
{
capture!(self, context, func);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("State Change Without Event")
}
fn description(&self) -> String {
String::from(
"There are state variable changes in this function but no event is emitted. Consider emitting an event to enable offchain indexers to track the changes.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::StateChangeWithoutEvent)
}
}
struct EventEmissionTracker {
does_emit_events: bool,
}
impl CallGraphVisitor for EventEmissionTracker {
fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
// Don't bother checking if we already know there is an event emitted
if self.does_emit_events {
return Ok(());
}
// Check for events
let events = ExtractEmitStatements::from(node).extracted;
if !events.is_empty() {
self.does_emit_events = true;
}
Ok(())
}
}
#[cfg(test)]
mod state_variable_changes_without_events_tests {
use crate::detect::{
detector::IssueDetector,
low::state_change_without_event::StateVariableChangesWithoutEventDetector,
};
#[test]
fn test_state_variable_changes_without_events() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/StateVariablesChangesWithoutEvents.sol",
);
let mut detector = StateVariableChangesWithoutEventDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 8);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/enumerable_loop_removal.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::{NodeID, NodeType};
use crate::{
capture,
context::{
browser::{ExtractMemberAccesses, GetClosestAncestorOfTypeX},
workspace::WorkspaceContext,
},
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct EnumerableLoopRemovalDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for EnumerableLoopRemovalDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// Find MemberAccesses with name `remove` and
// typeDescriptions.typeString.contains(EnumerableSet) for each one
// Find the closest ancestor of a loop
// if it exists, extract all `at` member accesses on the enumerableset
// If an `at` memberaccess also exists in the loop, add the remove to found_instances
context
.member_accesses()
.into_iter()
.filter(|member_access| {
member_access.member_name == "remove"
&& member_access
.type_descriptions
.type_string
.as_ref()
.is_some_and(|type_string| type_string.contains("EnumerableSet"))
})
.for_each(|member_access| {
let parent_loops = [
member_access.closest_ancestor_of_type(context, NodeType::ForStatement),
member_access.closest_ancestor_of_type(context, NodeType::WhileStatement),
member_access.closest_ancestor_of_type(context, NodeType::DoWhileStatement),
];
for parent_loop in parent_loops.into_iter().flatten() {
ExtractMemberAccesses::from(parent_loop).extracted.into_iter().for_each(
|at_member_access| {
if at_member_access.member_name == "at" {
capture!(self, context, member_access);
}
},
);
}
});
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("EnumerableSet.remove Corrupts Order")
}
fn description(&self) -> String {
String::from("If the order of an EnumerableSet is required, removing items in a loop using `at` and `remove` corrupts this order.
Consider using a different data structure or removing items by collecting them during the loop, then removing after the loop.")
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::EnumerableLoopRemoval)
}
}
#[cfg(test)]
mod enuemrable_loop_removal_tests {
use crate::detect::{detector::IssueDetector, high::EnumerableLoopRemovalDetector};
#[test]
fn test_enumerable_loop_detector() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/EnumerableSetIteration.sol",
);
let mut detector = EnumerableLoopRemovalDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 5);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/deprecated_oz_function.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::{
ast::{NodeID, NodeType},
capture,
context::{
browser::GetClosestAncestorOfTypeX,
workspace::{ASTNode, WorkspaceContext},
},
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct DeprecatedOZFunctionDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for DeprecatedOZFunctionDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for identifier in context.identifiers() {
// if source_unit has any ImportDirectives with absolute_path containing "openzeppelin"
// call identifier.accept(self)
if let Some(ASTNode::SourceUnit(source_unit)) =
identifier.closest_ancestor_of_type(context, NodeType::SourceUnit)
{
let import_directives = source_unit.import_directives();
if import_directives.iter().any(|directive| {
directive
.absolute_path
.as_ref()
.is_some_and(|path| path.contains("openzeppelin"))
}) && identifier.name == "_setupRole"
{
capture!(self, context, identifier);
}
} else {
// Optional: handle other cases, or do nothing
}
}
for member_access in context.member_accesses() {
// if source_unit has any ImportDirectives with absolute_path containing "openzeppelin"
// call member_access.accept(self)
if let Some(ASTNode::SourceUnit(source_unit)) =
member_access.closest_ancestor_of_type(context, NodeType::SourceUnit)
{
let import_directives = source_unit.import_directives();
if import_directives.iter().any(|directive| {
directive
.absolute_path
.as_ref()
.is_some_and(|path| path.contains("openzeppelin"))
}) && member_access.member_name == "safeApprove"
{
capture!(self, context, member_access);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn title(&self) -> String {
String::from("Deprecated OpenZeppelin Function")
}
fn description(&self) -> String {
String::from(
"Openzeppelin has deprecated several functions and replaced with newer versions. Please consult https://docs.openzeppelin.com/",
)
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::DeprecatedOzFunction)
}
}
#[cfg(test)]
mod deprecated_oz_functions_tests {
use crate::detect::detector::IssueDetector;
use super::DeprecatedOZFunctionDetector;
#[test]
fn test_deprecated_oz_functions_detector_by_loading_contract_directly() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/DeprecatedOZFunctions.sol",
);
let mut detector = DeprecatedOZFunctionDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 2);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/mcp/contract_surface/tool.rs:
--------------------------------------------------------------------------------
```rust
use super::render::*;
use crate::{
ast::{ASTNode, NodeID},
context::{
macros::{mcp_error, mcp_success},
mcp::{
MCPToolNamePool, ModelContextProtocolState, ModelContextProtocolTool,
contract_surface::util::{
get_classified_entrypoint_functions, get_inheritance_chain_info,
get_total_state_variables,
},
},
},
};
use indoc::indoc;
use rmcp::{
ErrorData as McpError, handler::server::wrapper::Parameters, model::CallToolResult, schemars,
};
use serde::Deserialize;
use std::sync::Arc;
#[derive(Clone)]
pub struct ContractSurfaceTool {
state: Arc<ModelContextProtocolState>,
}
#[derive(Deserialize, schemars::JsonSchema)]
pub struct ContractSurfacePayload {
/// The index of the compilation unit to analyze. Must be a positive integer starting from 1.
/// Use the project overview tool first to see all available compilation units and their
/// indices.
pub compilation_unit_index: usize,
/// The Node ID of the specific contract to analyze. Obtain this from the list contracts tool,
/// which returns Node IDs for all deployable contracts in the compilation unit. Each contract
/// has a unique Node ID within its compilation unit.
pub node_id: NodeID,
}
impl ModelContextProtocolTool for ContractSurfaceTool {
type Input = ContractSurfacePayload;
fn new(state: Arc<ModelContextProtocolState>) -> Self {
Self { state }
}
fn name(&self) -> String {
MCPToolNamePool::AderynContractSurfaceInspector.to_string()
}
fn description(&self) -> String {
indoc! {
"Analyzes the surface area of a specific deployable contract within a compilation unit. Returns details\
such as contract's state variables (own and inherited), and all entrypoint functions (own and inherited).\
Use the Node ID from the list contracts tool to specify which contract to analyze."
}
.to_string()
}
fn execute(&self, input: Parameters<Self::Input>) -> Result<CallToolResult, McpError> {
let payload = input.0;
if payload.compilation_unit_index < 1
|| payload.compilation_unit_index > self.state.contexts.len()
{
return mcp_error!(
"Invalid compilation unit index: {}. Must be in range [1, {}]",
payload.compilation_unit_index,
self.state.contexts.len()
);
}
let context = self
.state
.contexts
.get(payload.compilation_unit_index - 1)
.expect("Compilation unit index bounds check failed");
let Some(ASTNode::ContractDefinition(contract)) = context.nodes.get(&payload.node_id)
else {
return mcp_error!(
"Node ID {} does not correspond to a contract definition",
payload.node_id
);
};
let (filepath, _, _) = context.get_node_sort_key_from_capturable(&contract.into());
let total_state_variables = get_total_state_variables(context, contract);
let reversed_chain = get_inheritance_chain_info(context, contract)?;
let entrypoints = get_classified_entrypoint_functions(context, contract)?;
let contract_surface = ContractSurfaceBuilder::default()
.name(contract.name.clone())
.node_id(payload.node_id)
.filepath(filepath)
.compilation_unit_index(payload.compilation_unit_index)
.total_state_variables(total_state_variables)
.reversed_chain(reversed_chain)
.entrypoints(entrypoints)
.build()
.expect("failed to build contract surface");
mcp_success!(contract_surface)
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/abi_encode_packed_hash_collision.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::{
ast::NodeID,
capture,
context::workspace::WorkspaceContext,
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct AvoidAbiEncodePackedDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for AvoidAbiEncodePackedDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for member_access in context.member_accesses() {
// If the member_access's member_name = "encodePacked", loop through the argument_types
// and count how many of them contain any of the following in type_strings:
// ["bytes ", "[]", "string"]
// If the count is greater than 1, add the member_access to the found_abi_encode_packed
// vector
if member_access.member_name == "encodePacked" {
let mut count = 0;
let argument_types = member_access.argument_types.as_ref().unwrap();
for argument_type in argument_types {
if argument_type.type_string.as_ref().unwrap().contains("bytes ")
|| argument_type.type_string.as_ref().unwrap().contains("[]")
|| argument_type.type_string.as_ref().unwrap().contains("string")
{
count += 1;
}
}
if count > 1 {
capture!(self, context, member_access);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn title(&self) -> String {
String::from("`abi.encodePacked()` Hash Collision")
}
fn description(&self) -> String {
String::from(
"abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as `keccak256()`. \
Use `abi.encode()` instead which will pad items to 32 bytes, preventing hash collisions: https://docs.soliditylang.org/en/v0.8.13/abi-spec.html#non-standard-packed-mode. \
(e.g. `abi.encodePacked(0x123,0x456)` => `0x123456` => `abi.encodePacked(0x1,0x23456)`, but `abi.encode(0x123,0x456)` => `0x0...1230...456`). \
Unless there is a compelling reason, `abi.encode` should be preferred. If there is only one argument to `abi.encodePacked()` \
it can often be cast to `bytes()` or `bytes32()` instead: https://ethereum.stackexchange.com/questions/30912/how-to-compare-strings-in-solidity#answer-82739. \
If all arguments are strings and or bytes, `bytes.concat()` should be used instead.",
)
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::AbiEncodePackedHashCollision)
}
}
#[cfg(test)]
mod avoid_abi_encode_packed_tests {
use crate::detect::detector::IssueDetector;
use super::AvoidAbiEncodePackedDetector;
#[test]
fn test_avoid_abi_encode_packed_detectorby_by_loading_contract_directly() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/KeccakContract.sol",
);
let mut detector = AvoidAbiEncodePackedDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 3);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/out_of_order_retryable.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::{Expression, MemberAccess, NodeID};
use crate::{
capture,
context::{
browser::ExtractFunctionCalls,
graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
workspace::WorkspaceContext,
},
detect::{
detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
helpers,
},
};
use eyre::Result;
#[derive(Default)]
pub struct OutOfOrderRetryableDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for OutOfOrderRetryableDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for func in helpers::get_implemented_external_and_public_functions(context) {
let callgraphs =
CallGraphConsumer::get(context, &[&(func.into())], CallGraphDirection::Inward)?;
for callgraph in callgraphs {
let mut tracker = OutOfOrderRetryableTracker { number_of_retry_calls: 0 };
callgraph.accept(context, &mut tracker)?;
if tracker.number_of_retry_calls >= 2 {
capture!(self, context, func);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("Out of Order Retryable Transaction")
}
fn description(&self) -> String {
String::from("Do not rely on the order or successful execution of retryable tickets. Functions like \
createRetryableTicket, outboundTransferCustomRefund, unsafeCreateRetryableTicket are free to be re-tried in any
order if they fail in the first go. Since this operation happens off chain, the sequencer is in control of the
order of these transactions. Therefore, restrict the use to at most 1 ticket call per function.")
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::OutOfOrderRetryable)
}
}
struct OutOfOrderRetryableTracker {
number_of_retry_calls: usize,
}
const SEQUENCER_FUNCTIONS: [&str; 3] =
["createRetryableTicket", "outboundTransferCustomRefund", "unsafeCreateRetryableTicket"];
impl CallGraphVisitor for OutOfOrderRetryableTracker {
fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
if self.number_of_retry_calls >= 2 {
return Ok(());
}
let function_calls = ExtractFunctionCalls::from(node).extracted;
for func_call in function_calls {
if let Expression::MemberAccess(MemberAccess { member_name, .. }) =
func_call.expression.as_ref()
&& SEQUENCER_FUNCTIONS.iter().any(|f| f == member_name)
{
self.number_of_retry_calls += 1;
}
}
Ok(())
}
}
#[cfg(test)]
mod out_of_order_retryable_tests {
use crate::detect::{
detector::IssueDetector, high::out_of_order_retryable::OutOfOrderRetryableDetector,
};
#[test]
fn test_out_of_order_retryable() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/OutOfOrderRetryable.sol",
);
let mut detector = OutOfOrderRetryableDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 2);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/unchecked_low_level_call.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::{ASTNode, NodeID, NodeType};
use crate::{
capture,
context::{
browser::{GetClosestAncestorOfTypeX, GetImmediateParent},
workspace::WorkspaceContext,
},
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct UncheckedLowLevelCallDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for UncheckedLowLevelCallDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
let call_types = ["call", "staticcall", "delegatecall"];
for member_access in context.member_accesses() {
if call_types.iter().any(|&c| c == member_access.member_name)
&& member_access.expression.type_descriptions().is_some_and(|type_desc| {
type_desc.type_string.as_ref().is_some_and(|type_string| {
type_string == "address" || type_string == "address payable"
})
})
&& let Some(ASTNode::FunctionCall(func_call)) =
member_access.closest_ancestor_of_type(context, NodeType::FunctionCall)
{
// In most cases, it's enough to check if the function call's parent is Block
// But to cover this case - dst.call.value(msg.value)("");
// We need to also check for the possibility where the function call's parent is
// another function call and that has a direct parent of type block
if let Some(ASTNode::ExpressionStatement(e)) = func_call.parent(context)
&& e.parent(context).is_some_and(|node| node.node_type() == NodeType::Block)
{
capture!(self, context, func_call);
}
if let Some(ASTNode::FunctionCall(outside_parent)) = func_call.parent(context)
&& let Some(ASTNode::ExpressionStatement(e)) = outside_parent.parent(context)
&& e.parent(context).is_some_and(|node| node.node_type() == NodeType::Block)
{
capture!(self, context, func_call);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("Unchecked Low level calls")
}
fn description(&self) -> String {
String::from(
"The return value of the low-level call is not checked, so if the call fails, the Ether will be locked in the contract. If the low level is used to prevent blocking operations, consider logging failed calls. Ensure that the return value of a low-level call is checked or logged.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::UncheckedLowLevelCall)
}
}
#[cfg(test)]
mod unchecked_low_level_calls_tests {
use crate::detect::{
detector::IssueDetector, high::unchecked_low_level_call::UncheckedLowLevelCallDetector,
};
#[test]
fn test_unchecked_low_level_calls() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/UncheckedCalls.sol",
);
let mut detector = UncheckedLowLevelCallDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 9);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unused_public_function.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::{
ast::{FunctionKind, NodeID, Visibility},
capture,
context::{
browser::GetClosestAncestorOfTypeX,
workspace::{ASTNode, WorkspaceContext},
},
detect::{
detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
helpers::count_identifiers_that_reference_an_id,
},
};
use eyre::Result;
#[derive(Default)]
pub struct UnusedPublicFunctionDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for UnusedPublicFunctionDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
let unreferenced_public_functions =
context.function_definitions().into_iter().filter(|&function| {
matches!(function.visibility, Visibility::Public)
&& !matches!(function.kind(), &FunctionKind::Constructor)
&& function.overrides.is_none()
&& !function.is_virtual
&& count_identifiers_that_reference_an_id(context, function.id) == 0
});
for unreferenced_public_function in unreferenced_public_functions {
if let Some(ASTNode::ContractDefinition(parent_contract)) = unreferenced_public_function
.closest_ancestor_of_type(context, crate::ast::NodeType::ContractDefinition)
&& parent_contract.is_abstract
{
continue;
}
capture!(self, context, unreferenced_public_function);
}
Ok(!self.found_instances.is_empty())
}
fn title(&self) -> String {
String::from("Public Function Not Used Internally")
}
fn description(&self) -> String {
String::from(
"If a function is marked public but is not used internally, consider marking it as `external`.",
)
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::UnusedPublicFunction)
}
}
#[cfg(test)]
mod useless_public_function_tests {
use crate::detect::detector::IssueDetector;
use super::UnusedPublicFunctionDetector;
#[test]
fn test_useless_public_functions_by_loading_contract_directly() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/Counter.sol",
);
let mut detector = UnusedPublicFunctionDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 1);
}
#[test]
fn test_useless_public_functions_does_not_capture_abstract_contract_functions() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/AbstractContract.sol",
);
let mut detector = UnusedPublicFunctionDetector::default();
let found = detector.detect(&context).unwrap();
assert!(!found);
assert_eq!(detector.instances().len(), 0);
}
#[test]
fn test_useless_public_functions_does_not_capture_virtual_or_overriding_functions() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/PublicFunction.sol",
);
let mut detector = UnusedPublicFunctionDetector::default();
let found = detector.detect(&context).unwrap();
assert!(!found);
assert_eq!(detector.instances().len(), 0);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high.rs:
--------------------------------------------------------------------------------
```rust
pub(crate) mod abi_encode_packed_hash_collision;
pub(crate) mod arbitrary_transfer_from;
pub(crate) mod const_func_changes_state;
pub(crate) mod contract_locks_ether;
pub(crate) mod dangerous_unary_operator;
pub(crate) mod delegate_call_unchecked_address;
pub(crate) mod delete_nested_mapping;
pub(crate) mod dynamic_array_length_assignment;
pub(crate) mod enumerable_loop_removal;
pub(crate) mod eth_send_unchecked_address;
pub(crate) mod experimental_encoder;
pub(crate) mod function_selector_collision;
pub(crate) mod incorrect_caret_operator;
pub(crate) mod incorrect_erc20_interface;
pub(crate) mod incorrect_erc721_interface;
pub(crate) mod incorrect_shift_order;
pub(crate) mod misused_boolean;
pub(crate) mod msg_value_in_loops;
pub(crate) mod multiple_constructors;
pub(crate) mod nested_struct_in_mapping;
pub(crate) mod out_of_order_retryable;
pub(crate) mod pre_declared_variable_usage;
pub(crate) mod reentrancy_state_change;
pub(crate) mod reused_contract_name;
pub(crate) mod rtlo;
pub(crate) mod selfdestruct;
pub(crate) mod signed_integer_storage_array;
pub(crate) mod state_variable_shadowing;
pub(crate) mod storage_array_memory_edit;
pub(crate) mod strict_equality_contract_balance;
pub(crate) mod tautological_compare;
pub(crate) mod tautology_or_contradiction;
pub(crate) mod tx_origin_used_for_auth;
pub(crate) mod unchecked_low_level_call;
pub(crate) mod unchecked_send;
pub(crate) mod unprotected_initializer;
pub(crate) mod unsafe_casting;
pub(crate) mod weak_randomness;
pub(crate) mod yul_return;
pub use abi_encode_packed_hash_collision::AvoidAbiEncodePackedDetector;
pub use arbitrary_transfer_from::ArbitraryTransferFromDetector;
pub use const_func_changes_state::ConstantFunctionChangesStateDetector;
pub use contract_locks_ether::ContractLocksEtherDetector;
pub use dangerous_unary_operator::DangerousUnaryOperatorDetector;
pub use delegate_call_unchecked_address::DelegateCallUncheckedAddressDetector;
pub use delete_nested_mapping::DeletionNestedMappingDetector;
pub use dynamic_array_length_assignment::DynamicArrayLengthAssignmentDetector;
pub use enumerable_loop_removal::EnumerableLoopRemovalDetector;
pub use eth_send_unchecked_address::SendEtherNoChecksDetector;
pub use experimental_encoder::ExperimentalEncoderDetector;
pub use function_selector_collision::FunctionSelectorCollisionDetector;
pub use incorrect_caret_operator::IncorrectUseOfCaretOperatorDetector;
pub use incorrect_erc20_interface::IncorrectERC20InterfaceDetector;
pub use incorrect_erc721_interface::IncorrectERC721InterfaceDetector;
pub use incorrect_shift_order::IncorrectShiftOrderDetector;
pub use misused_boolean::MisusedBooleanDetector;
pub use msg_value_in_loops::MsgValueUsedInLoopDetector;
pub use multiple_constructors::MultipleConstructorsDetector;
pub use nested_struct_in_mapping::NestedStructInMappingDetector;
pub use out_of_order_retryable::OutOfOrderRetryableDetector;
pub use pre_declared_variable_usage::PreDeclaredLocalVariableUsageDetector;
pub use reentrancy_state_change::ReentrancyStateChangeDetector;
pub use reused_contract_name::ReusedContractNameDetector;
pub use rtlo::RTLODetector;
pub use selfdestruct::SelfdestructDetector;
pub use signed_integer_storage_array::StorageSignedIntegerArrayDetector;
pub use state_variable_shadowing::StateVariableShadowingDetector;
pub use storage_array_memory_edit::StorageArrayMemoryEditDetector;
pub use strict_equality_contract_balance::DangerousStrictEqualityOnBalanceDetector;
pub use tautological_compare::TautologicalCompareDetector;
pub use tautology_or_contradiction::TautologyOrContraditionDetector;
pub use tx_origin_used_for_auth::TxOriginUsedForAuthDetector;
pub use unchecked_low_level_call::UncheckedLowLevelCallDetector;
pub use unchecked_send::UncheckedSendDetector;
pub use unprotected_initializer::UnprotectedInitializerDetector;
pub use unsafe_casting::UnsafeCastingDetector;
pub use weak_randomness::WeakRandomnessDetector;
pub use yul_return::YulReturnDetector;
```
--------------------------------------------------------------------------------
/aderyn_driver/src/process.rs:
--------------------------------------------------------------------------------
```rust
use crate::{
compile,
config::supplement_values_from_aderyn_toml,
driver::{CliArgsCommonConfig, CliArgsInputConfig},
};
use aderyn_core::{
context::{
graph::{LegacyWorkspaceCallGraph, Transpose, WorkspaceCallGraphs},
router::Router,
workspace::WorkspaceContext,
},
stats,
};
use solidity_ast::ProjectConfigInput;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
pub struct WorkspaceContextWrapper {
pub contexts: Vec<WorkspaceContext>,
pub root_path: PathBuf,
pub project_config: ProjectConfigInput,
}
pub struct PreprocessedConfig {
pub root_path: PathBuf,
pub src: Option<String>,
pub include: Option<Vec<String>>,
pub exclude: Option<Vec<String>>,
}
pub fn make_context(
args: &CliArgsInputConfig,
common: &CliArgsCommonConfig,
) -> Result<WorkspaceContextWrapper, Box<dyn std::error::Error + Send + Sync>> {
// Preprocess config by supplementing CLI args with aderyn.toml if exists
let preprocessed_config = obtain_config_values(args.clone())?;
let root_path = preprocessed_config.root_path.clone();
// Compilation steps:
// 1. Processes the above preprocessed config by translating them to runtime values Thanks to
// Cyfrin/solidity-ast-rs
// 2. Parse those files and prepare ASTs.
let (mut contexts, project_config) =
compile::compile_project(preprocessed_config, common.lsp, common.verbose)?;
// Only make this an error when it's not in LSP mode
if !common.lsp && contexts.iter().all(|c| c.src_filepaths.is_empty()) {
let error = "No solidity files found in given scope!";
eprintln!("{}", error);
return Err(error.into());
}
// Supplement the context
// 1. Inject nSLOC stats
// 2. Collect lines marked by aderyn-ignore-line, aderyn-ignore-next-line
// 3. Inject Legacy Callgraph
// 4. Inject Router
// 5. Inject New Callgraph
for context in contexts.iter_mut() {
let absolute_root_path = &ensure_valid_root_path(&root_path);
let stats = stats::collect_stats(absolute_root_path.as_path(), common.skip_cloc, context);
let sloc_stats: HashMap<String, usize> =
stats.iter().map(|(key, value)| (key.to_owned(), value.code)).collect();
let ignore_line_stats: HashMap<String, Vec<stats::IgnoreLine>> =
stats.iter().map(|(key, value)| (key.to_owned(), value.ignore_lines.clone())).collect();
context.set_sloc_stats(sloc_stats);
context.set_ignore_lines_stats(ignore_line_stats);
let inward_callgraph = LegacyWorkspaceCallGraph::from_context(context)?;
let outward_callgraph =
LegacyWorkspaceCallGraph { raw_callgraph: inward_callgraph.raw_callgraph.reverse() };
context.inward_callgraph = Some(inward_callgraph);
context.outward_callgraph = Some(outward_callgraph);
let router = Router::build(context);
context.router = Some(router);
let callgraphs = WorkspaceCallGraphs::build(context);
context.callgraphs = Some(callgraphs);
}
Ok(WorkspaceContextWrapper { contexts, root_path, project_config })
}
/// Supplement the CLI arguments with values from aderyn.toml
fn obtain_config_values(
args: CliArgsInputConfig,
) -> Result<PreprocessedConfig, Box<dyn std::error::Error + Send + Sync>> {
let root_path = PathBuf::from(&args.root);
let aderyn_path = root_path.join("aderyn.toml");
let current = PreprocessedConfig {
root_path,
src: args.src,
exclude: args.path_excludes,
include: args.path_includes,
};
// Process aderyn.toml if it exists
if aderyn_path.exists() {
return supplement_values_from_aderyn_toml(current);
}
Ok(current)
}
fn ensure_valid_root_path(root_path: &Path) -> PathBuf {
if !root_path.exists() {
eprintln!("{} does not exist!", root_path.to_string_lossy());
std::process::exit(1);
}
std::fs::canonicalize(root_path).unwrap()
}
```
--------------------------------------------------------------------------------
/aderyn_core/templates/mcp-tool-response/contract_surface.md:
--------------------------------------------------------------------------------
```markdown
## Contract Surface Inspector for {{ name }}
*Contract name:* {{ name }}
*Contract ID:* {{ node_id }}
*Compilation Unit index:* {{ compilation_unit_index }}
*Filepath:* {{ filepath }}
### C3 Linearized Inheritance
After performing C3 linearization on the main contract {{ name }}, the following chain is the end result. It goes from the most base parent class at the beginning to the most derived class at the end of the list chain.
The last contract class in the chain is {{ name }} because it is the most derived contract class in its hierarchy.
List of names of contract class in the inheritance chain of {{ name }} along with their filepath.
**Total contracts in inheritance chain:** {{ reversed_chain.len() }}
{% for c in reversed_chain %}
{{ loop.index }}. {{ c.name }} | Node ID: {{ c.node_id }} | Filepath: {{ c.filepath }}
{% endfor %}
### State variables
State variables of a contract include all variables defined in the contract itself plus those inherited from parent classes.
{% if total_state_variables == 0 %}
*No state variables found*
{% else %}
The following code snippets show all state variables defined in each contract class within the inheritance chain.
{% for c in reversed_chain %}
{{ loop.index }}. {{ c.name }}
{% if c.state_variables.len() == 0 %}
No state variables found defined in {{ c.name }}
{% else %}
```solidity
{% for s in c.state_variables %}
{{ s }}
{% endfor %}
```
{% endif %}
{% endfor %}
{% endif %}
### Entrypoint functions
In Solidity, entrypoint functions are functions that can be called from outside the contract or serve as initial execution points. The main types of entrypoint functions are external functions, public functions, fallback functions, and receive functions.
The following is a list of entrypoint functions for {{ name }} with their corresponding Node IDs and the contract class in the inheritance chain where each function is defined.
If a function has been overridden, it won't be listed in here. This list contains all the functions after filtering out the ones that are actually called.
#### External Functions
{% if entrypoints.external_functions.len() == 0 %}
*No external functions found*
{% else %}
{% for func in entrypoints.external_functions %}
{{ loop.index }}. **{{ func.name }}** | Function's Node Id: {{ func.node_id }} | Containing contract class: {{ func.containing_contract.name }} | Containing contract's Node Id: {{ func.containing_contract.node_id }}
{% endfor %}
{% endif %}
#### Public Functions
{% if entrypoints.public_functions.len() == 0 %}
*No public functions found*
{% else %}
{% for func in entrypoints.public_functions %}
{{ loop.index }}. **{{ func.name }}** | Function's Node Id: {{ func.node_id }} | Containing contract class: {{ func.containing_contract.name }} | Containing contract's Node Id: {{ func.containing_contract.node_id }}
{% endfor %}
{% endif %}
#### Fallback Function
{% if let Some(fallback_function) = entrypoints.fallback_function %}
Node Id: {{ fallback_function.node_id }} | Containing contract class: {{ fallback_function.containing_contract.name }} | Containing contract's Node Id: {{ fallback_function.containing_contract.node_id }}
{% else %}
*No fallback function found.*
{% endif %}
#### Receive Function
{% if let Some(receive_function) = entrypoints.receive_function %}
Node Id: {{ receive_function.node_id }} | Containing contract class: {{ receive_function.containing_contract.name }} | Containing contract's Node Id: {{ receive_function.containing_contract.node_id }}
{% else %}
*No receive function found.*
{% endif %}
### Suggestion for next steps:
Try to explore callgraphs starting from a given entrypoint function. To do that, use the callgraph provider tool and pass the entrypoint function's Node ID, and the Node ID of the original deployable contract (same one used to call this contract surface tool). NOT just the containing contract's Node ID.
A good analysis explores the project on a per callgraph basis for entrypoints of interest. This provides a holistic picture and therefore less false positives.
```
--------------------------------------------------------------------------------
/benchmarks/report/index.html:
--------------------------------------------------------------------------------
```html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Index - Criterion.rs</title>
<style type="text/css">
body {
font: 14px Helvetica Neue;
text-rendering: optimizelegibility;
}
.body {
width: 960px;
margin: auto;
}
a:link {
color: #1F78B4;
text-decoration: none;
}
h2 {
font-size: 36px;
font-weight: 300;
}
h3 {
font-size: 24px;
font-weight: 300;
}
#footer {
height: 40px;
background: #888;
color: white;
font-size: larger;
font-weight: 300;
}
#footer a {
color: white;
text-decoration: underline;
}
#footer p {
text-align: center
}
table {
border-collapse: collapse;
}
table,
th,
td {
border: 1px solid #888;
}
</style>
</head>
<body>
<div class="body">
<h2>Criterion.rs Benchmark Index</h2>
See individual benchmark pages below for more details.
<ul>
<li><a href="../aderyn/report/index.html">aderyn</a></li>
<li><a href="../arbitrary-transfer-from/report/index.html">arbitrary-transfer-from</a></li>
<li><a href="../avoid-abi-encode-packed/report/index.html">avoid-abi-encode-packed</a></li>
<li><a href="../block-timestamp-deadline/report/index.html">block-timestamp-deadline</a></li>
<li><a href="../centralization-risk/report/index.html">centralization-risk</a></li>
<li><a href="../constants-instead-of-literals/report/index.html">constants-instead-of-literals</a></li>
<li><a href="../delegate-call-in-loop/report/index.html">delegate-call-in-loop</a></li>
<li><a href="../deprecated-oz-functions/report/index.html">deprecated-oz-functions</a></li>
<li><a href="../ecrecover/report/index.html">ecrecover</a></li>
<li><a href="../empty-block/report/index.html">empty-block</a></li>
<li><a href="../hello_world/report/index.html">hello_world</a></li>
<li><a href="../inconsistent-type-names/report/index.html">inconsistent-type-names</a></li>
<li><a href="../large-numeric-literal/report/index.html">large-numeric-literal</a></li>
<li><a href="../non-reentrant-before-others/report/index.html">non-reentrant-before-others</a></li>
<li><a href="../push-zero-opcode/report/index.html">push-zero-opcode</a></li>
<li><a href="../require-with-string/report/index.html">require-with-string</a></li>
<li><a href="../solmate-safe-transfer-lib/report/index.html">solmate-safe-transfer-lib</a></li>
<li><a href="../unindexed-events/report/index.html">unindexed-events</a></li>
<li><a href="../unprotected-initializer/report/index.html">unprotected-initializer</a></li>
<li><a href="../unsafe-erc20-functions/report/index.html">unsafe-erc20-functions</a></li>
<li><a href="../unsafe-oz-erc721-mint/report/index.html">unsafe-oz-erc721-mint</a></li>
<li><a href="../unspecific-solidity-pragma/report/index.html">unspecific-solidity-pragma</a></li>
<li><a href="../useless-internal-function/report/index.html">useless-internal-function</a></li>
<li><a href="../useless-modifier/report/index.html">useless-modifier</a></li>
<li><a href="../useless-public-function/report/index.html">useless-public-function</a></li>
<li><a href="../zero-address-check/report/index.html">zero-address-check</a></li>
</ul>
</div>
<div id="footer">
<p>This report was generated by
<a href="https://github.com/bheisler/criterion.rs">Criterion.rs</a>, a statistics-driven benchmarking
library in Rust.</p>
</div>
</body>
</html>
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/mcp/contract_surface/util.rs:
--------------------------------------------------------------------------------
```rust
use super::render::*;
use crate::{
ast::{ASTNode, ContractDefinition, FunctionKind, NodeType, Visibility},
context::{
browser::{ExtractVariableDeclarations, GetClosestAncestorOfTypeX},
workspace::WorkspaceContext,
},
};
use rmcp::ErrorData as McpError;
pub fn get_total_state_variables(
context: &WorkspaceContext,
original_contract: &ContractDefinition,
) -> usize {
let Some(state_vars) =
original_contract.get_all_state_variables_in_linearized_base_contracts_chain(context)
else {
return 0;
};
state_vars.len()
}
pub fn get_classified_entrypoint_functions(
context: &WorkspaceContext,
original_contract: &ContractDefinition,
) -> Result<EntrypointFunctions, McpError> {
let Some(functions) = context.entrypoint_functions(original_contract) else {
// TODO: investigate when you hit this case, it has been a while since I made the router
return Ok(EntrypointFunctions::default());
};
let mut public_functions_info = vec![];
let mut external_functions_info = vec![];
let mut receive_function_info = None;
let mut fallback_function_info = None;
for func in functions {
let Some(ASTNode::ContractDefinition(container)) =
func.closest_ancestor_of_type(context, NodeType::ContractDefinition)
else {
continue;
};
let container = ContainingContractBuilder::default()
.name(container.name.clone())
.node_id(container.id)
.build()
.expect("failed to build container");
let function_info = FunctionInfoBuilder::default()
.name(func.name.clone())
.node_id(func.id)
.containing_contract(container)
.build()
.expect("failed to build function info");
match *func.kind() {
FunctionKind::Function => match func.visibility {
Visibility::Public => {
public_functions_info.push(function_info);
}
Visibility::External => {
external_functions_info.push(function_info);
}
Visibility::Internal | Visibility::Private => {} // not an entrypoint
},
FunctionKind::Receive => {
receive_function_info = Some(function_info);
}
FunctionKind::Fallback => {
fallback_function_info = Some(function_info);
}
FunctionKind::Constructor => {} // For now, constructor is not an entrypoint
FunctionKind::FreeFunction => unreachable!(), // Free function is never an entrypoint
};
}
let entrypoints = EntrypointFunctionsBuilder::default()
.external_functions(external_functions_info)
.public_functions(public_functions_info)
.fallback_function(fallback_function_info)
.receive_function(receive_function_info)
.build()
.expect("failed to build entrypoints in inheritance chain");
Ok(entrypoints)
}
pub fn get_inheritance_chain_info(
context: &WorkspaceContext,
original_contract: &ContractDefinition,
) -> Result<Vec<ContractInfo>, McpError> {
let mut reversed_chain = vec![];
for contract in original_contract.c3(context).collect::<Vec<_>>().into_iter().rev() {
let variables = ExtractVariableDeclarations::from(contract).extracted;
let state_variables = variables
.iter()
.filter(|v| v.state_variable)
.map(|v| v.name.clone())
.collect::<Vec<_>>();
let (filepath, _, _) = context.get_node_sort_key_from_capturable(&contract.into());
let contract_info = ContractInfoBuilder::default()
.name(contract.name.clone())
.node_id(contract.id)
.state_variables(state_variables)
.filepath(filepath)
.build()
.expect("failed to build contract info in inheritance chain");
reversed_chain.push(contract_info);
}
Ok(reversed_chain)
}
```
--------------------------------------------------------------------------------
/tests/ast/override.json:
--------------------------------------------------------------------------------
```json
{"absolutePath":"a","exportedSymbols":{"A":[5],"B":[16],"C":[29]},"id":30,"nodeType":"SourceUnit","nodes":[{"abstract":false,"baseContracts":[],"canonicalName":"A","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":5,"linearizedBaseContracts":[5],"name":"A","nameLocation":"9:1:1","nodeType":"ContractDefinition","nodes":[{"body":{"id":3,"nodeType":"Block","src":"44:2:1","statements":[]},"functionSelector":"a399b6a2","id":4,"implemented":true,"kind":"function","modifiers":[],"name":"faa","nameLocation":"23:3:1","nodeType":"FunctionDefinition","parameters":{"id":1,"nodeType":"ParameterList","parameters":[],"src":"26:2:1"},"returnParameters":{"id":2,"nodeType":"ParameterList","parameters":[],"src":"44:0:1"},"scope":5,"src":"14:32:1","stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"scope":30,"src":"0:48:1","usedErrors":[]},{"abstract":true,"baseContracts":[{"baseName":{"id":6,"name":"A","nameLocations":["72:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":5,"src":"72:1:1"},"id":7,"nodeType":"InheritanceSpecifier","src":"72:1:1"}],"canonicalName":"B","contractDependencies":[],"contractKind":"contract","fullyImplemented":false,"id":16,"linearizedBaseContracts":[16,5],"name":"B","nameLocation":"67:1:1","nodeType":"ContractDefinition","nodes":[{"functionSelector":"c2985578","id":10,"implemented":false,"kind":"function","modifiers":[],"name":"foo","nameLocation":"86:3:1","nodeType":"FunctionDefinition","parameters":{"id":8,"nodeType":"ParameterList","parameters":[],"src":"89:2:1"},"returnParameters":{"id":9,"nodeType":"ParameterList","parameters":[],"src":"106:0:1"},"scope":16,"src":"77:30:1","stateMutability":"nonpayable","virtual":true,"visibility":"public"},{"baseFunctions":[4],"body":{"id":14,"nodeType":"Block","src":"148:2:1","statements":[]},"functionSelector":"a399b6a2","id":15,"implemented":true,"kind":"function","modifiers":[],"name":"faa","nameLocation":"118:3:1","nodeType":"FunctionDefinition","overrides":{"id":12,"nodeType":"OverrideSpecifier","overrides":[],"src":"139:8:1"},"parameters":{"id":11,"nodeType":"ParameterList","parameters":[],"src":"121:2:1"},"returnParameters":{"id":13,"nodeType":"ParameterList","parameters":[],"src":"148:0:1"},"scope":16,"src":"109:41:1","stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"scope":30,"src":"49:103:1","usedErrors":[]},{"abstract":false,"baseContracts":[{"baseName":{"id":17,"name":"B","nameLocations":["167:1:1"],"nodeType":"IdentifierPath","referencedDeclaration":16,"src":"167:1:1"},"id":18,"nodeType":"InheritanceSpecifier","src":"167:1:1"}],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":29,"linearizedBaseContracts":[29,16,5],"name":"C","nameLocation":"162:1:1","nodeType":"ContractDefinition","nodes":[{"baseFunctions":[10],"body":{"id":22,"nodeType":"Block","src":"203:3:1","statements":[]},"functionSelector":"c2985578","id":23,"implemented":true,"kind":"function","modifiers":[],"name":"foo","nameLocation":"181:3:1","nodeType":"FunctionDefinition","overrides":{"id":20,"nodeType":"OverrideSpecifier","overrides":[],"src":"194:8:1"},"parameters":{"id":19,"nodeType":"ParameterList","parameters":[],"src":"184:2:1"},"returnParameters":{"id":21,"nodeType":"ParameterList","parameters":[],"src":"203:0:1"},"scope":29,"src":"172:34:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"baseFunctions":[15],"body":{"id":27,"nodeType":"Block","src":"239:3:1","statements":[]},"functionSelector":"a399b6a2","id":28,"implemented":true,"kind":"function","modifiers":[],"name":"faa","nameLocation":"217:3:1","nodeType":"FunctionDefinition","overrides":{"id":25,"nodeType":"OverrideSpecifier","overrides":[],"src":"230:8:1"},"parameters":{"id":24,"nodeType":"ParameterList","parameters":[],"src":"220:2:1"},"returnParameters":{"id":26,"nodeType":"ParameterList","parameters":[],"src":"239:0:1"},"scope":29,"src":"208:34:1","stateMutability":"nonpayable","virtual":false,"visibility":"public"}],"scope":30,"src":"153:91:1","usedErrors":[]}],"src":"0:245:1"}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/costly_loop.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, convert::identity, error::Error};
use crate::ast::{ASTNode, NodeID};
use crate::{capture, context::browser::ApproximateStorageChangeFinder};
use crate::{
context::{
graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
workspace::WorkspaceContext,
},
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct CostlyLoopDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for CostlyLoopDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// Investigate for loops to check for storage writes
for for_statement in context.for_statements() {
if changes_state(context, &(for_statement.into())).is_some_and(identity) {
capture!(self, context, for_statement);
}
}
// Investigate while loops to check for storage writes
for while_statement in context.while_statements() {
if changes_state(context, &(while_statement.into())).is_some_and(identity) {
capture!(self, context, while_statement);
}
}
// Investigate the do while loops to check for storage writes
for do_while_statement in context.do_while_statements() {
if changes_state(context, &(do_while_statement.into())).is_some_and(identity) {
capture!(self, context, do_while_statement);
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("Costly operations inside loop")
}
fn description(&self) -> String {
String::from(
"Invoking `SSTORE` operations in loops may waste gas. Use a local variable to hold the loop computation result.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
IssueDetectorNamePool::CostlyLoop.to_string()
}
}
fn changes_state(context: &WorkspaceContext, ast_node: &ASTNode) -> Option<bool> {
// Now, investigate the function to see if there is scope for any state variable changes
let callgraphs =
CallGraphConsumer::get(context, &[ast_node], CallGraphDirection::Inward).ok()?;
for callgraph in callgraphs {
let mut tracker = StateVariableChangeTracker { state_var_has_changed: false, context };
callgraph.accept(context, &mut tracker).ok()?;
if tracker.state_var_has_changed {
return Some(true);
}
}
Some(false)
}
struct StateVariableChangeTracker<'a> {
state_var_has_changed: bool,
context: &'a WorkspaceContext,
}
impl CallGraphVisitor for StateVariableChangeTracker<'_> {
fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
if self.state_var_has_changed {
return Ok(());
}
// Check for state variable changes
let finder = ApproximateStorageChangeFinder::from(self.context, node);
if finder.state_variables_have_been_manipulated() {
self.state_var_has_changed = true;
}
Ok(())
}
}
#[cfg(test)]
mod costly_operations_inside_loops_tests {
use crate::detect::{detector::IssueDetector, low::costly_loop::CostlyLoopDetector};
#[test]
fn test_costly_operations_inside_loops() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/CostlyOperationsInsideLoops.sol",
);
let mut detector = CostlyLoopDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 1);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/pre_declared_variable_usage.rs:
--------------------------------------------------------------------------------
```rust
use std::{
collections::{BTreeMap, HashSet},
error::Error,
};
use crate::ast::{ASTNode, NodeID};
use crate::{
capture,
context::{
browser::{ExtractIdentifiers, ExtractVariableDeclarations},
workspace::WorkspaceContext,
},
detect::{
detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
helpers,
},
};
use eyre::Result;
// HOW TO USE THIS TEMPLATE:
// 1. Copy this file and rename it to the snake_case version of the issue you are detecting.
// 2. Rename the PreDeclaredLocalVariableUsageDetector struct and impl to your new issue name.
// 3. Add this file and detector struct to the mod.rs file in the same directory.
// 4. Implement the detect function to find instances of the issue.
#[derive(Default)]
pub struct PreDeclaredLocalVariableUsageDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for PreDeclaredLocalVariableUsageDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// Since this is restricted to local variables, we examine each function independently
for function in context.function_definitions().into_iter().filter(|&f| f.implemented) {
let local_variable_declaration_ids = ExtractVariableDeclarations::from(function)
.extracted
.iter()
.map(|vd| vd.id)
.collect::<HashSet<_>>();
let used_local_variables = ExtractIdentifiers::from(function).extracted;
let used_local_variables = used_local_variables
.iter()
.filter(|identifier| {
identifier.referenced_declaration.is_some_and(|referenced_declaration| {
local_variable_declaration_ids.contains(&referenced_declaration)
})
})
.collect::<HashSet<_>>();
for used in used_local_variables {
if let Some(id) = used.referenced_declaration
&& let Some(ASTNode::VariableDeclaration(variable_declaration)) =
context.nodes.get(&id)
{
let used_offset = helpers::get_node_offset(&used.into());
let declaration_offset = helpers::get_node_offset(&variable_declaration.into());
if let (Some(used_offset), Some(declaration_offset)) =
(used_offset, declaration_offset)
&& used_offset < declaration_offset
{
capture!(self, context, used);
}
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("Usage of variable before declaration")
}
fn description(&self) -> String {
String::from("Declare the variable before using it to avoid unintended consequences.")
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
IssueDetectorNamePool::PreDeclaredLocalVariableUsage.to_string()
}
}
#[cfg(test)]
mod pre_declared_variable_usage_tests {
use crate::detect::{
detector::IssueDetector,
high::pre_declared_variable_usage::PreDeclaredLocalVariableUsageDetector,
};
#[test]
fn test_pre_declared_variable_usage() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/PreDeclaredVarUsage.sol",
);
let mut detector = PreDeclaredLocalVariableUsageDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 1);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/strict_equality_contract_balance.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::{Expression, NodeID};
use crate::{
capture,
context::workspace::WorkspaceContext,
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct DangerousStrictEqualityOnBalanceDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for DangerousStrictEqualityOnBalanceDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// When you have found an instance of the issue,
// use the following macro to add it to `found_instances`:
//
// capture!(self, context, item);
for binary_operation in context
.binary_operations()
.into_iter()
.filter(|&op| op.operator == "==" || op.operator == "!=")
{
for expr in [
binary_operation.left_expression.as_ref(),
binary_operation.right_expression.as_ref(),
] {
if let Expression::MemberAccess(member_access) = expr
&& member_access.member_name == "balance"
&& member_access.expression.as_ref().type_descriptions().is_some_and(
|type_desc| {
type_desc.type_string.as_ref().is_some_and(|type_string| {
// For older solc versions when you say this.balance, "this" is
// of type contract XXX
type_string.starts_with("contract ")
// In newers solidity versions, you say address(this).balance or payable(address(this)).balance
|| type_string == "address"
|| type_string == "address payable"
})
},
)
{
capture!(self, context, binary_operation);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("Dangerous strict equality checks on contract balances")
}
fn description(&self) -> String {
String::from(
"A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
IssueDetectorNamePool::StrictEqualityContractBalance.to_string()
}
}
#[cfg(test)]
mod strict_equality_contract_balance_tests {
use crate::detect::{
detector::IssueDetector,
high::strict_equality_contract_balance::DangerousStrictEqualityOnBalanceDetector,
};
#[test]
fn test_strict_equality_contract_balance1() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/DangerousStrictEquality1.sol",
);
let mut detector = DangerousStrictEqualityOnBalanceDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 1);
}
#[test]
fn test_strict_equality_contract_balance2() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/DangerousStrictEquality2.sol",
);
let mut detector = DangerousStrictEqualityOnBalanceDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 2);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/unused_state_variable.rs:
--------------------------------------------------------------------------------
```rust
use std::{
collections::{BTreeMap, BTreeSet},
error::Error,
};
use crate::ast::{ASTNode, ContractKind, NodeID, NodeType, Visibility};
use crate::{
capture,
context::{
browser::{
ExtractReferencedDeclarations, ExtractVariableDeclarations, GetClosestAncestorOfTypeX,
},
workspace::WorkspaceContext,
},
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct UnusedStateVariablesDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for UnusedStateVariablesDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// Collect all referencedDeclaration IDs and StateVariableDeclarationIDs
let mut all_referenced_declarations = BTreeSet::new();
let mut all_state_variable_declarations = BTreeSet::new();
for source_unit in context.source_units() {
let referenced_declarations =
ExtractReferencedDeclarations::from(source_unit).extracted;
all_referenced_declarations.extend(referenced_declarations);
let variable_declarations = ExtractVariableDeclarations::from(source_unit).extracted;
all_state_variable_declarations.extend(
variable_declarations
.into_iter()
.filter(|v| {
v.state_variable
&& (v.visibility == Visibility::Private
|| v.visibility == Visibility::Internal)
})
.map(|v| v.id),
)
}
// Now, retain only the ones that have not been referenced
all_state_variable_declarations.retain(|v| !all_referenced_declarations.contains(v));
for unused_state_var_id in all_state_variable_declarations {
if let Some(node) = context.nodes.get(&unused_state_var_id) {
if let Some(ASTNode::ContractDefinition(contract)) =
node.closest_ancestor_of_type(context, NodeType::ContractDefinition)
{
// If this variable is defined inside a contract, make sure it's not an abstract
// contract before capturing it
if !contract.is_abstract && contract.kind == ContractKind::Contract {
capture!(self, context, node);
}
} else {
// Otherwise, just capture it !
capture!(self, context, node);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("Unused State Variable")
}
fn description(&self) -> String {
String::from(
"State variable appears to be unused. No analysis has been performed to see if any inline assembly \
references it. Consider removing this unused variable.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::UnusedStateVariable)
}
}
#[cfg(test)]
mod unused_detector_tests {
use crate::detect::{
detector::IssueDetector, low::unused_state_variable::UnusedStateVariablesDetector,
};
#[test]
fn test_unused_state_variables() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/UnusedStateVariables.sol",
);
let mut detector = UnusedStateVariablesDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 4);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/assert_state_change.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, convert::identity, error::Error};
use crate::ast::{Expression, Identifier, NodeID};
use crate::{
capture,
context::workspace::WorkspaceContext,
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct AssertStateChangeDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for AssertStateChangeDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for function_call in context.function_calls() {
if let Expression::Identifier(Identifier { name, .. }) =
function_call.expression.as_ref()
&& name == "assert"
&& function_call.arguments_change_contract_state(context).is_some_and(identity)
{
capture!(self, context, function_call);
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("State change in `assert()` statement")
}
fn description(&self) -> String {
String::from(
"An argument to `assert()` modifies the state. Use `require` for invariants modifying state.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::AssertStateChange)
}
}
mod assert_state_change_tracker {
use crate::{
ast::{ASTNode, FunctionCall},
context::{
browser::ApproximateStorageChangeFinder,
graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
workspace::WorkspaceContext,
},
};
struct StateVariableChangeTracker<'a> {
has_some_state_variable_changed: bool,
context: &'a WorkspaceContext,
}
impl CallGraphVisitor for StateVariableChangeTracker<'_> {
fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
if self.has_some_state_variable_changed {
return Ok(());
}
let finder = ApproximateStorageChangeFinder::from(self.context, node);
if finder.state_variables_have_been_manipulated() {
self.has_some_state_variable_changed = true;
}
Ok(())
}
}
impl FunctionCall {
pub fn arguments_change_contract_state(&self, context: &WorkspaceContext) -> Option<bool> {
let arguments =
self.arguments.clone().into_iter().map(|n| n.into()).collect::<Vec<ASTNode>>();
let ast_nodes: &[&ASTNode] = &(arguments.iter().collect::<Vec<_>>());
let callgraphs =
CallGraphConsumer::get(context, ast_nodes, CallGraphDirection::Inward).ok()?;
for callgraph in callgraphs {
let mut tracker =
StateVariableChangeTracker { has_some_state_variable_changed: false, context };
callgraph.accept(context, &mut tracker).ok()?;
if tracker.has_some_state_variable_changed {
return Some(true);
}
}
Some(false)
}
}
}
#[cfg(test)]
mod assert_state_changes_tests {
use crate::detect::{
detector::IssueDetector, low::assert_state_change::AssertStateChangeDetector,
};
#[test]
fn test_assert_state_change() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/AssertStateChange.sol",
);
let mut detector = AssertStateChangeDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 1);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/msg_value_in_loops.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, convert::identity, error::Error};
use crate::ast::{ASTNode, Expression, NodeID};
use crate::{
capture,
context::{
browser::ExtractMemberAccesses,
graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
workspace::WorkspaceContext,
},
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct MsgValueUsedInLoopDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for MsgValueUsedInLoopDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// Investigate for loops to check for usage of `msg.value`
for for_statement in context.for_statements() {
if uses_msg_value(context, &(for_statement.into())).is_some_and(identity) {
capture!(self, context, for_statement);
}
}
// Investigate while loops to check for usage of `msg.value`
for while_statement in context.while_statements() {
if uses_msg_value(context, &(while_statement.into())).is_some_and(identity) {
capture!(self, context, while_statement);
}
}
// Investigate the do while loops to check for usage of `msg.value`
for do_while_statement in context.do_while_statements() {
if uses_msg_value(context, &(do_while_statement.into())).is_some_and(identity) {
capture!(self, context, do_while_statement);
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("Loop contains `msg.value`")
}
fn description(&self) -> String {
String::from(
"Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
IssueDetectorNamePool::MsgValueInLoop.to_string()
}
}
fn uses_msg_value(context: &WorkspaceContext, ast_node: &ASTNode) -> Option<bool> {
let callgraphs =
CallGraphConsumer::get(context, &[ast_node], CallGraphDirection::Inward).ok()?;
for callgraph in callgraphs {
let mut tracker = MsgValueTracker::default();
callgraph.accept(context, &mut tracker).ok()?;
if tracker.has_msg_value {
return Some(true);
}
}
Some(false)
}
#[derive(Default)]
struct MsgValueTracker {
has_msg_value: bool,
}
impl CallGraphVisitor for MsgValueTracker {
fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
if !self.has_msg_value
&& ExtractMemberAccesses::from(node).extracted.iter().any(|member_access| {
member_access.member_name == "value"
&& if let Expression::Identifier(identifier) = member_access.expression.as_ref()
{
identifier.name == "msg"
} else {
false
}
})
{
self.has_msg_value = true;
}
Ok(())
}
}
#[cfg(test)]
mod msg_value_in_loop_detector {
use crate::detect::{
detector::IssueDetector, high::msg_value_in_loops::MsgValueUsedInLoopDetector,
};
#[test]
fn test_msg_value_in_loop() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/MsgValueInLoop.sol",
);
let mut detector = MsgValueUsedInLoopDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 4);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/router/modifier_calls.rs:
--------------------------------------------------------------------------------
```rust
use super::Router;
use crate::{
ast::{
ASTNode, ContractDefinition, ContractKind, IdentifierOrIdentifierPath, ModifierDefinition,
ModifierInvocation, NodeID, NodeType,
},
context::{browser::GetClosestAncestorOfTypeX, workspace::WorkspaceContext},
};
use std::collections::{HashMap, hash_map::Entry};
impl Router {
pub(super) fn _resolve_modifier_call<'a>(
&self,
context: &'a WorkspaceContext,
base_contract: &'a ContractDefinition,
modifier_call: &'a ModifierInvocation,
) -> Option<&'a ModifierDefinition> {
// check if it's illegal base contract type
if !base_contract.is_deployable_contract() {
return None;
}
// check if it's illegal value - i.e function call that cannot be called from the base
// contract must be discarded
if let Some(ASTNode::ContractDefinition(caller_contract)) =
modifier_call.closest_ancestor_of_type(context, NodeType::ContractDefinition)
{
if caller_contract.kind == ContractKind::Contract
&& !caller_contract.is_in(context, base_contract)
{
return None;
} else if caller_contract.kind == ContractKind::Library {
// If an internal modifier call happens from a library, suspect cannot be overridden
// As of now, libraries do not have inheritance
//
// NOTE: for this case, we don't check that the modifier call from library can
// actually happen and is trigger(able) by the base contract.
let aux_modifier = modifier_call.suspected_target_modifier(context)?;
return Some(aux_modifier);
}
}
self.perform_mc_lookup_through_inheritance_tree_and_fallback_to_suspect(
context,
base_contract,
modifier_call,
)
}
fn perform_mc_lookup_through_inheritance_tree_and_fallback_to_suspect<'a>(
&self,
context: &'a WorkspaceContext,
base_contract: &'a ContractDefinition,
modifier_call: &'a ModifierInvocation,
) -> Option<&'a ModifierDefinition> {
let aux_modifier = modifier_call.suspected_target_modifier(context)?;
let selectorish = aux_modifier.selectorish();
let base_index = self.modifier_calls.get(&base_contract.id)?;
let resolve = |starting_point: &ContractDefinition| -> Option<&ModifierDefinition> {
let starting_point = starting_point.id;
let lookup_index = base_index.routes.get(&starting_point)?;
match lookup_index.get(&selectorish) {
Some(modifier_id) => match context.nodes.get(modifier_id) {
Some(ASTNode::ModifierDefinition(modifier_def)) => Some(modifier_def),
_ => None,
},
// if not found in lookup fallback to aux function (suspect function)
None => Some(aux_modifier),
}
};
// TODO: Investigate when other enumeration can be triggered.
if let IdentifierOrIdentifierPath::IdentifierPath(p) = &modifier_call.modifier_name {
// Ex: `B.modify` is a full path so then the suspected target must be right.
if p.name.contains('.') {
return Some(aux_modifier);
}
// Ex: `modify`
return resolve(base_contract);
}
None
}
}
pub(super) fn build_mc_router_for_contract(
context: &WorkspaceContext,
base_contract: &ContractDefinition,
) -> HashMap<NodeID, HashMap<String, NodeID>> {
let c3 = base_contract.c3(context).collect::<Vec<_>>();
let mut base_routes = HashMap::new();
for (idx, starting_point) in c3.iter().enumerate() {
let mut routes = HashMap::new();
for contract in c3.iter().skip(idx) {
for modifier in contract.modifier_definitions() {
if let Entry::Vacant(e) = routes.entry(modifier.selectorish()) {
e.insert(modifier.id);
}
}
}
base_routes.insert(starting_point.id, routes);
}
base_routes
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/delete_nested_mapping.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::{
ASTNode, Expression, Identifier, IndexAccess, Mapping, NodeID, TypeName, UserDefinedTypeName,
VariableDeclaration,
};
use crate::{
capture,
context::workspace::WorkspaceContext,
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct DeletionNestedMappingDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for DeletionNestedMappingDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for delete_operation in
context.unary_operations().into_iter().filter(|op| op.operator == "delete")
{
if let Expression::IndexAccess(IndexAccess { base_expression, .. }) =
delete_operation.sub_expression.as_ref()
&& let Expression::Identifier(Identifier {
referenced_declaration: Some(referenced_id),
type_descriptions,
..
}) = base_expression.as_ref()
{
// Check if we're deleting a value from mapping
if type_descriptions
.type_string
.as_ref()
.is_some_and(|type_string| type_string.starts_with("mapping"))
{
// Check if the value in the mapping is of type struct that has a member
// which is also a mapping
if let Some(ASTNode::VariableDeclaration(VariableDeclaration {
type_name: Some(TypeName::Mapping(Mapping { value_type, .. })),
..
})) = context.nodes.get(referenced_id)
&& let TypeName::UserDefinedTypeName(UserDefinedTypeName {
referenced_declaration,
..
}) = value_type.as_ref()
&& let Some(ASTNode::StructDefinition(structure)) =
context.nodes.get(referenced_declaration)
{
// Check that a member of a struct is of type mapping
if structure.members.iter().any(|member| {
member
.type_descriptions
.type_string
.as_ref()
.is_some_and(|type_string| type_string.starts_with("mapping"))
}) {
capture!(self, context, delete_operation);
}
}
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("Deletion from a nested mapping")
}
fn description(&self) -> String {
String::from(
"A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
IssueDetectorNamePool::DeleteNestedMapping.to_string()
}
}
#[cfg(test)]
mod deletion_nested_mapping_tests {
use crate::detect::{
detector::IssueDetector, high::delete_nested_mapping::DeletionNestedMappingDetector,
};
#[test]
fn test_deletion_nested_mapping() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/DeletionNestedMappingStructureContract.sol",
);
let mut detector = DeletionNestedMappingDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 1);
}
}
```
--------------------------------------------------------------------------------
/aderyn_driver/src/interface/lsp.rs:
--------------------------------------------------------------------------------
```rust
use aderyn_core::report::*;
use std::{collections::BTreeMap, path::Path};
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url};
/// Report structure that is tailored to aid LSP
pub struct LspReport {
pub high_issues: HighIssues,
pub low_issues: LowIssues,
pub diagnostics: BTreeMap<Url, Vec<Diagnostic>>,
}
impl LspReport {
pub fn from(low_issues: LowIssues, high_issues: HighIssues, root_rel_path: &Path) -> Self {
fn create_diagnostic_from_issue(
issue_body: &IssueBody,
instance: &IssueInstance,
severity: DiagnosticSeverity,
root_rel_path: &Path,
) -> Option<(Url, Diagnostic)> {
// Line number
let line_no = instance.line_no.checked_sub(1)?;
// Character position and range from the start of the line number
let (pos_start, pos_range) = instance.src_char2.split_once(':')?;
let pos_start = pos_start.parse::<u32>().unwrap_or_default().checked_sub(1)?;
let pos_range = pos_range.parse::<u32>().unwrap_or_default();
// Craft the diagnostic message
let mut message = format!("Title: {}\n", issue_body.title);
if !issue_body.description.is_empty() {
message.push_str(&format!("\nDescription: {}\n", issue_body.description));
}
if let Some(hint) = instance.hint.clone()
&& !hint.is_empty()
{
message.push_str(&format!("\nHint: {}\n", hint));
}
message.push_str(&format!(
"\nTo ignore this warning, add:\n\n// aderyn-ignore-next-line({})\n\nor mark as false positive:\n\n// aderyn-fp-next-line({})\n\n",
issue_body.detector_name,
issue_body.detector_name,
));
// Make the diagnostic that LSP can understand
let diagnostic = Diagnostic {
range: Range {
start: Position { line: line_no as u32, character: pos_start },
end: Position { line: line_no as u32, character: pos_start + pos_range },
},
severity: Some(severity),
message,
code: None,
code_description: None,
source: Some("Aderyn".to_string()),
related_information: None,
tags: None,
data: None,
};
let mut full_contract_path = root_rel_path.to_path_buf();
full_contract_path.push(instance.contract_path.clone());
let full_contract_path = full_contract_path.canonicalize().ok()?;
let full_contract_path_string = full_contract_path.to_string_lossy().to_string();
let file_uri = Url::parse(&format!("file://{}", &full_contract_path_string)).ok()?;
Some((file_uri, diagnostic))
}
let mut diagnostics = BTreeMap::new();
for issue_body in &high_issues.issues {
for instance in &issue_body.instances {
let Some((file_url, diagnostic)) = create_diagnostic_from_issue(
issue_body,
instance,
DiagnosticSeverity::WARNING,
root_rel_path,
) else {
continue;
};
let file_diagnostics: &mut Vec<Diagnostic> =
diagnostics.entry(file_url).or_default();
file_diagnostics.push(diagnostic);
}
}
for issue_body in &low_issues.issues {
for instance in &issue_body.instances {
let Some((file_url, diagnostic)) = create_diagnostic_from_issue(
issue_body,
instance,
DiagnosticSeverity::INFORMATION,
root_rel_path,
) else {
continue;
};
let file_diagnostics: &mut Vec<Diagnostic> =
diagnostics.entry(file_url).or_default();
file_diagnostics.push(diagnostic);
}
}
Self { low_issues, high_issues, diagnostics }
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/eth_send_unchecked_address.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::NodeID;
use crate::{
capture,
context::{
browser::ExtractModifierInvocations,
graph::CallGraphVisitor,
workspace::{ASTNode, WorkspaceContext},
},
detect::{
detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
helpers,
},
};
use eyre::Result;
#[derive(Default)]
pub struct SendEtherNoChecksDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for SendEtherNoChecksDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for (func, callgraphs) in context.entrypoints_with_callgraphs() {
for callgraph in callgraphs {
let mut tracker = AddressChecksAndCallWithValueTracker::default();
callgraph.accept(context, &mut tracker)?;
// Hacky way to check if the modifier is a know msg.sender checking modifier
// This is because our Callgraph doesn't navigate inside contracts that are outside
// the scope, this includes imported contracts.
let has_oz_modifier =
ExtractModifierInvocations::from(func).extracted.iter().any(|invocation| {
invocation.modifier_name.name().contains("onlyRole")
|| invocation.modifier_name.name() == "onlyOwner"
|| invocation.modifier_name.name() == "requiresAuth"
});
if tracker.sends_native_eth
&& !tracker.has_binary_checks_on_some_address
&& !has_oz_modifier
{
capture!(self, context, func);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("ETH transferred without address checks")
}
fn description(&self) -> String {
String::from(
"Consider introducing checks for `msg.sender` to ensure the recipient of the money is as intended.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
IssueDetectorNamePool::EthSendUncheckedAddress.to_string()
}
}
#[derive(Default)]
pub struct AddressChecksAndCallWithValueTracker {
pub has_binary_checks_on_some_address: bool,
pub sends_native_eth: bool,
}
impl CallGraphVisitor for AddressChecksAndCallWithValueTracker {
fn visit_any(&mut self, node: &ASTNode) -> eyre::Result<()> {
if !self.has_binary_checks_on_some_address
&& helpers::has_binary_checks_on_some_address(node)
{
self.has_binary_checks_on_some_address = true;
}
if !self.sends_native_eth && helpers::has_calls_that_sends_native_eth(node) {
self.sends_native_eth = true;
}
eyre::Ok(())
}
}
#[cfg(test)]
mod send_ether_no_checks_detector_tests {
use crate::detect::{
detector::IssueDetector, high::eth_send_unchecked_address::SendEtherNoChecksDetector,
};
#[test]
fn test_send_ether_no_checks_lib_import() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/SendEtherNoChecksLibImport.sol",
);
let mut detector = SendEtherNoChecksDetector::default();
let found = detector.detect(&context).unwrap();
assert!(!found);
assert_eq!(detector.instances().len(), 0);
}
#[test]
fn test_send_ether_no_checks() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/SendEtherNoChecks.sol",
);
let mut detector = SendEtherNoChecksDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 3);
}
}
```
--------------------------------------------------------------------------------
/tests/hardhat-js-playground/test/Lock.js:
--------------------------------------------------------------------------------
```javascript
const {
time,
loadFixture,
} = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
const { expect } = require("chai");
describe("Lock", function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployOneYearLockFixture() {
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
const ONE_GWEI = 1_000_000_000;
const lockedAmount = ONE_GWEI;
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
// Contracts are deployed using the first signer/account by default
const [owner, otherAccount] = await ethers.getSigners();
const Lock = await ethers.getContractFactory("Lock");
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
return { lock, unlockTime, lockedAmount, owner, otherAccount };
}
describe("Deployment", function () {
it("Should set the right unlockTime", async function () {
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);
expect(await lock.unlockTime()).to.equal(unlockTime);
});
it("Should set the right owner", async function () {
const { lock, owner } = await loadFixture(deployOneYearLockFixture);
expect(await lock.owner()).to.equal(owner.address);
});
it("Should receive and store the funds to lock", async function () {
const { lock, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
expect(await ethers.provider.getBalance(lock.target)).to.equal(
lockedAmount
);
});
it("Should fail if the unlockTime is not in the future", async function () {
// We don't use the fixture here because we want a different deployment
const latestTime = await time.latest();
const Lock = await ethers.getContractFactory("Lock");
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
"Unlock time should be in the future"
);
});
});
describe("Withdrawals", function () {
describe("Validations", function () {
it("Should revert with the right error if called too soon", async function () {
const { lock } = await loadFixture(deployOneYearLockFixture);
await expect(lock.withdraw()).to.be.revertedWith(
"You can't withdraw yet"
);
});
it("Should revert with the right error if called from another account", async function () {
const { lock, unlockTime, otherAccount } = await loadFixture(
deployOneYearLockFixture
);
// We can increase the time in Hardhat Network
await time.increaseTo(unlockTime);
// We use lock.connect() to send a transaction from another account
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
"You aren't the owner"
);
});
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
const { lock, unlockTime } = await loadFixture(
deployOneYearLockFixture
);
// Transactions are sent using the first signer by default
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).not.to.be.reverted;
});
});
describe("Events", function () {
it("Should emit an event on withdrawals", async function () {
const { lock, unlockTime, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw())
.to.emit(lock, "Withdrawal")
.withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
});
});
describe("Transfers", function () {
it("Should transfer the funds to the owner", async function () {
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).to.changeEtherBalances(
[owner, lock],
[lockedAmount, -lockedAmount]
);
});
});
});
});
```
--------------------------------------------------------------------------------
/aderyn_core/src/ast/impls/node/contracts.rs:
--------------------------------------------------------------------------------
```rust
use crate::{ast::*, visitor::ast_visitor::*};
use eyre::Result;
impl Node for ContractDefinitionNode {
fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
match self {
ContractDefinitionNode::UsingForDirective(using_for_directive) => {
using_for_directive.accept(visitor)
}
ContractDefinitionNode::StructDefinition(struct_definition) => {
struct_definition.accept(visitor)
}
ContractDefinitionNode::EnumDefinition(enum_definition) => {
enum_definition.accept(visitor)
}
ContractDefinitionNode::VariableDeclaration(variable_declaration) => {
variable_declaration.accept(visitor)
}
ContractDefinitionNode::EventDefinition(event_definition) => {
event_definition.accept(visitor)
}
ContractDefinitionNode::FunctionDefinition(function_definition) => {
function_definition.accept(visitor)
}
ContractDefinitionNode::ModifierDefinition(modifier_definition) => {
modifier_definition.accept(visitor)
}
ContractDefinitionNode::ErrorDefinition(error_definition) => {
error_definition.accept(visitor)
}
ContractDefinitionNode::UserDefinedValueTypeDefinition(
user_defined_value_type_definition,
) => user_defined_value_type_definition.accept(visitor),
}
}
fn accept_id(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
visitor.visit_node_id(self.get_node_id())?;
Ok(())
}
}
impl Node for InheritanceSpecifier {
fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
if visitor.visit_inheritance_specifier(self)? {
match &self.base_name {
UserDefinedTypeNameOrIdentifierPath::UserDefinedTypeName(type_name) => {
type_name.accept(visitor)?
}
UserDefinedTypeNameOrIdentifierPath::IdentifierPath(identifier_path) => {
identifier_path.accept(visitor)?;
}
};
if self.arguments.is_some() {
list_accept(self.arguments.as_ref().unwrap(), visitor)?;
}
}
self.accept_metadata(visitor)?;
visitor.end_visit_inheritance_specifier(self)
}
fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
if let Some(base_name_id) = self.base_name.get_node_id() {
visitor.visit_immediate_children(self.id, vec![base_name_id])?;
}
let mut argument_ids: Vec<NodeID> = vec![];
if let Some(arguments) = &self.arguments {
for expression in arguments {
let node_id = expression.get_node_id();
if let Some(node_id) = node_id {
argument_ids.push(node_id)
}
}
}
visitor.visit_immediate_children(self.id, argument_ids)?;
Ok(())
}
macros::accept_id!();
}
impl Node for ContractDefinition {
fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
if visitor.visit_contract_definition(self)? {
if self.documentation.is_some() {
self.documentation.as_ref().unwrap().accept(visitor)?;
}
list_accept(&self.base_contracts, visitor)?;
list_accept(&self.nodes, visitor)?;
}
self.accept_metadata(visitor)?;
visitor.end_visit_contract_definition(self)
}
fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
// TODO: Skipping documentation for now
let mut base_contracts_ids = vec![];
for base_contract in &self.base_contracts {
base_contracts_ids.push(base_contract.id);
}
visitor.visit_immediate_children(self.id, base_contracts_ids)?;
let mut node_ids = vec![];
for node in &self.nodes {
if let Some(node_id) = node.get_node_id() {
node_ids.push(node_id);
}
}
visitor.visit_immediate_children(self.id, node_ids)?;
Ok(())
}
macros::accept_id!();
}
```
--------------------------------------------------------------------------------
/reports/adhoc-sol-files-highs-only-report.json:
--------------------------------------------------------------------------------
```json
{
"files_summary": {
"total_source_units": 20,
"total_sloc": 245
},
"files_details": {
"files_details": [
{
"file_path": "Counter.sol",
"n_sloc": 20
},
{
"file_path": "DemoASTNodes.sol",
"n_sloc": 31
},
{
"file_path": "Helper.sol",
"n_sloc": 8
},
{
"file_path": "InconsistentUints.sol",
"n_sloc": 17
},
{
"file_path": "InternalFunctions.sol",
"n_sloc": 22
},
{
"file_path": "OnceModifierExample.sol",
"n_sloc": 8
},
{
"file_path": "StateVariables.sol",
"n_sloc": 58
},
{
"file_path": "inheritance/ExtendedInheritance.sol",
"n_sloc": 17
},
{
"file_path": "inheritance/IContractInheritance.sol",
"n_sloc": 4
},
{
"file_path": "inheritance/InheritanceBase.sol",
"n_sloc": 8
},
{
"file_path": "multiple-versions/0.4/A.sol",
"n_sloc": 5
},
{
"file_path": "multiple-versions/0.4/B.sol",
"n_sloc": 5
},
{
"file_path": "multiple-versions/0.5/A.sol",
"n_sloc": 5
},
{
"file_path": "multiple-versions/0.5/B.sol",
"n_sloc": 7
},
{
"file_path": "multiple-versions/0.6/A.sol",
"n_sloc": 5
},
{
"file_path": "multiple-versions/0.6/B.sol",
"n_sloc": 5
},
{
"file_path": "multiple-versions/0.7/A.sol",
"n_sloc": 5
},
{
"file_path": "multiple-versions/0.7/B.sol",
"n_sloc": 5
},
{
"file_path": "multiple-versions/0.8/A.sol",
"n_sloc": 5
},
{
"file_path": "multiple-versions/0.8/B.sol",
"n_sloc": 5
}
]
},
"issue_count": {
"high": 2,
"low": 0
},
"high_issues": {
"issues": [
{
"title": "`delegatecall` to an Arbitrary Address",
"description": "Making a `delegatecall` to an arbitrary address without any checks is dangerous. Consider adding requirements on the target address.",
"detector_name": "delegate-call-unchecked-address",
"instances": [
{
"contract_path": "inheritance/ExtendedInheritance.sol",
"line_no": 14,
"src": "391:15",
"src_char": "391:15"
}
]
},
{
"title": "Unchecked Low level calls",
"description": "The return value of the low-level call is not checked, so if the call fails, the Ether will be locked in the contract. If the low level is used to prevent blocking operations, consider logging failed calls. Ensure that the return value of a low-level call is checked or logged.",
"detector_name": "unchecked-low-level-call",
"instances": [
{
"contract_path": "inheritance/ExtendedInheritance.sol",
"line_no": 16,
"src": "488:71",
"src_char": "488:71"
}
]
}
]
},
"low_issues": {
"issues": []
},
"detectors_used": [
"abi-encode-packed-hash-collision",
"arbitrary-transfer-from",
"unprotected-initializer",
"unsafe-casting",
"enumerable-loop-removal",
"experimental-encoder",
"incorrect-shift-order",
"storage-array-memory-edit",
"multiple-constructors",
"reused-contract-name",
"nested-struct-in-mapping",
"selfdestruct",
"dynamic-array-length-assignment",
"incorrect-caret-operator",
"yul-return",
"state-variable-shadowing",
"unchecked-send",
"misused-boolean",
"eth-send-unchecked-address",
"delegate-call-unchecked-address",
"tautological-compare",
"rtlo",
"dangerous-unary-operator",
"tautology-or-contradiction",
"strict-equality-contract-balance",
"signed-integer-storage-array",
"weak-randomness",
"pre-declared-local-variable-usage",
"delete-nested-mapping",
"tx-origin-used-for-auth",
"msg-value-in-loop",
"contract-locks-ether",
"incorrect-erc721-interface",
"incorrect-erc20-interface",
"out-of-order-retryable",
"constant-function-changes-state",
"function-selector-collision",
"unchecked-low-level-call",
"reentrancy-state-change"
]
}
```
--------------------------------------------------------------------------------
/aderyn/src/mcp.rs:
--------------------------------------------------------------------------------
```rust
use aderyn_driver::{SingletonMcpServer, driver};
use indoc::indoc;
use rmcp::{
ServiceExt,
transport::{
StreamableHttpService, streamable_http_server::session::local::LocalSessionManager,
},
};
use tokio::runtime::Builder;
/// Starts an MCP server with streamable HTTP transport on given port
pub fn spin_up_http_stream_mcp_server(args: driver::Args, port: u16) {
let mcp_server = driver::create_mcp_server(args).unwrap_or_else(|| {
eprintln!("Unable to generate workspace context. Likely, issue compiling solidity files.");
std::process::exit(1);
});
let async_runtime = Builder::new_multi_thread()
.worker_threads(20)
.thread_name("aderyn-http-mcp-server-async-runtime")
.thread_stack_size(3 * 1024 * 1024)
.enable_all()
.build()
.expect("unable to start async runtime");
eprintln!(
indoc! {"
Dear human,
The MCP Server is now running at:
http://127.0.0.1:{}/mcp
You can connect using any MCP-compatible client—such as an editor, an AI agent,
or any other tool that supports the protocol.
If you'd simply like to explore the available tools, you can use the free MCP
Inspector by running:
npx -y @modelcontextprotocol/inspector
in another terminal, and then enter the server URL shown above.
⚠️ Live reload is disabled to keep session data consistent.
Restart the MCP server whenever you need to apply changes from updated files.
"},
port
);
async_runtime.block_on(async move {
let mcp_server = SingletonMcpServer::new(mcp_server);
let service = StreamableHttpService::new(
move || Ok(mcp_server.clone()),
LocalSessionManager::default().into(),
Default::default(),
);
let router = axum::Router::new().nest_service("/mcp", service);
let tcp_listener =
tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)).await.unwrap();
let _ = axum::serve(tcp_listener, router).with_graceful_shutdown(shutdown_signal()).await;
});
// dbg!(args.input_config);
}
pub fn spin_up_stdio_mcp_server(args: driver::Args) {
let mcp_server = driver::create_mcp_server(args).unwrap_or_else(|| {
eprintln!("Unable to generate workspace context. Likely, issue compiling solidity files.");
std::process::exit(1);
});
let async_runtime = Builder::new_multi_thread()
.worker_threads(20)
.thread_name("aderyn-stdio-mcp-async-runtime")
.thread_stack_size(3 * 1024 * 1024)
.enable_all()
.build()
.expect("unable to start async runtime");
eprintln!(indoc! {"
Dear human,
The MCP Server is now running on STDIO.
You can connect using any MCP-compatible client—such as an editor, an AI agent,
or any other tool that supports the protocol.
If you'd simply like to explore the available tools, you can use the free MCP
Inspector by running:
npx -y @modelcontextprotocol/inspector
⚠️ Live reload is disabled to keep session data consistent.
Restart the MCP server whenever you need to apply changes from updated files.
"});
let stdio = || (tokio::io::stdin(), tokio::io::stdout());
async_runtime.block_on(async move {
let mcp_server = SingletonMcpServer::new(mcp_server);
let service = mcp_server
.serve(stdio())
.await
.inspect_err(|e| {
eprintln!("serving error: {:?}", e);
})
.expect("stdout");
service.waiting().await.expect("failed to start mcp server on stdio");
});
}
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c().await.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
use tokio::signal::unix::{SignalKind, signal};
let mut term = signal(SignalKind::terminate()).expect("failed to install SIGTERM handler");
term.recv().await;
};
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
eprintln!("Signal received, shutting down gracefully...");
std::process::exit(0);
}
```
--------------------------------------------------------------------------------
/tools/xtask/src/cut_release.rs:
--------------------------------------------------------------------------------
```rust
use std::{io::BufRead, time::Duration};
use xshell::{Shell, cmd};
use crate::flags::CutRelease;
pub fn cut_release(cut_release: CutRelease) -> anyhow::Result<()> {
let sh = Shell::new()?;
sh.change_dir(env!("CARGO_MANIFEST_DIR"));
sh.change_dir("../../");
// Wait for existing release completion
wait_for_release_completion(&sh)?;
// Sanity checks and syncs
sync_tags(&sh)?;
perform_prechecks(&sh)?;
// Release process
dry_run(&sh, &cut_release)?;
kick_off_release(&sh, &cut_release)?;
// Wait for release completion
println!("Waiting for release completion...");
std::thread::sleep(Duration::from_secs(10 * 60));
wait_for_release_completion(&sh)?;
// Regenerate sarif report (it would be broken because version number is contained)
regenerate_sarif_report(&sh)?;
Ok(())
}
fn wait_for_release_completion(sh: &Shell) -> anyhow::Result<()> {
let poll_time = Duration::from_secs(12);
// Check if actions are still pending
let actions_completed = {
let cmd = cmd!(sh, "gh run list --workflow release.yml");
let x = cmd.output()?.stdout.to_vec();
let stdout = String::from_utf8_lossy(&x);
stdout.lines().filter(|l| !l.is_empty()).all(|l| l.starts_with("completed"))
};
if !actions_completed {
println!(
"A release or a release plan is in progress ... [next poll: {}s]",
poll_time.as_secs()
);
std::thread::sleep(Duration::from_secs(12));
wait_for_release_completion(sh)?;
return Ok(());
}
Ok(())
}
fn kick_off_release(sh: &Shell, cut_release: &CutRelease) -> anyhow::Result<()> {
let execute_cmd = if cut_release.patch {
cmd!(sh, "cargo release patch --no-publish --exclude xtask --execute")
} else if cut_release.minor {
cmd!(sh, "cargo release minor --no-publish --exclude xtask --execute")
} else {
unreachable!()
};
println!("Kick off the release process\n\taderyn\n?[y/N]");
let mut line = String::new();
let stdin = std::io::stdin();
stdin.lock().read_line(&mut line).unwrap();
if line.contains("y") {
println!("Kicked-off release process!");
let d = execute_cmd.stdin(line.clone());
d.run()?;
} else {
println!("Declined release process!");
}
Ok(())
}
fn dry_run(sh: &Shell, cut_release: &CutRelease) -> anyhow::Result<()> {
let dry_run_command = if cut_release.patch {
cmd!(sh, "cargo release patch --no-publish --exclude xtask --no-tag")
} else if cut_release.minor {
cmd!(sh, "cargo release minor --no-publish --exclude xtask --no-tag")
} else {
unreachable!();
};
dry_run_command.run()?;
Ok(())
}
fn sync_tags(sh: &Shell) -> anyhow::Result<()> {
let sync = cmd!(sh, "git fetch --prune-tags origin");
sync.run()?;
Ok(())
}
fn regenerate_sarif_report(sh: &Shell) -> anyhow::Result<()> {
let regen = cmd!(sh, "cargo blesspr");
regen.run()?;
Ok(())
}
fn perform_prechecks(sh: &Shell) -> anyhow::Result<()> {
// Exit if not on dev branch
let curr_branch = {
let cmd = cmd!(sh, "git rev-parse --abbrev-ref HEAD");
let output = cmd.output()?;
String::from_utf8(output.stdout)?
};
if curr_branch.trim() != "dev" {
eprintln!("Please switch to dev branch and retry!. Curr branch: {}", curr_branch.trim());
std::process::exit(1);
}
// Error out if there are untracked files/staging changes
let uncommitted_stuff = {
let cmd = cmd!(sh, "git status --porcelain");
let output = cmd.output()?;
String::from_utf8(output.stdout)?
};
if !uncommitted_stuff.is_empty() {
eprintln!("Please clear your staging area and retry!");
std::process::exit(1);
}
// Error if dev branch is not in sync with remote
let local_commit_hash = {
let cmd = cmd!(sh, "git rev-parse dev");
let output = cmd.output()?;
String::from_utf8(output.stdout)?
};
let remote_commit_hash = {
let cmd = cmd!(sh, "git rev-parse origin/dev");
let output = cmd.output()?;
String::from_utf8(output.stdout)?
};
if remote_commit_hash != local_commit_hash {
eprintln!("dev branch is not in sync with origin. Do that and retry!");
std::process::exit(1);
}
Ok(())
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/uninitialized_local_variable.rs:
--------------------------------------------------------------------------------
```rust
use std::{
collections::{BTreeMap, HashSet},
error::Error,
};
use crate::ast::{ASTNode, NodeID};
use crate::{
capture,
context::{browser::ExtractReferencedDeclarations, workspace::WorkspaceContext},
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct UninitializedLocalVariableDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for UninitializedLocalVariableDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// Assumption:
// VariableDeclarationStatements consists of statements that look like `uint x;` `uint y,
// z;`, `uint p = 12;` but are not declared at the contract level (state level) but
// rather within functions and modifiers
let mut potentially_uninitialized_local_variables = HashSet::new();
for variable_declaration_statement in context
.variable_declaration_statements()
.into_iter()
.filter(|s| s.initial_value.is_none())
{
potentially_uninitialized_local_variables.extend(
variable_declaration_statement.declarations.iter().flat_map(|s| {
if let Some(s) = s {
return Some(s.id);
}
None
}),
);
}
// We can filter out the initialized variables by looking at LHS of assignments.
// This trick works for local variables because it's not possible to have structs, mappings,
// dynamic arrays declared local to the function.
for assignment in context.assignments() {
let references =
ExtractReferencedDeclarations::from(assignment.left_hand_side.as_ref()).extracted;
potentially_uninitialized_local_variables.retain(|v| !references.contains(v));
}
// Blacklist variables assigned via Yul Assignments
let mut blacklist_variable_names = HashSet::new();
for yul_assignment in context.yul_assignments() {
blacklist_variable_names
.extend(yul_assignment.variable_names.iter().map(|v| v.name.clone()))
}
for id in potentially_uninitialized_local_variables {
if let Some(ASTNode::VariableDeclaration(v)) = context.nodes.get(&id)
&& !blacklist_variable_names.contains(&v.name)
{
// Ignore memory structs because they can have an initializeMethod of their own.
// So not covered under the assignment operator
if v.type_descriptions
.type_string
.as_ref()
.is_some_and(|type_string| !type_string.contains("struct "))
{
capture!(self, context, v);
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("Uninitialized Local Variable")
}
fn description(&self) -> String {
String::from(
"Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero to improve code readability.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::UninitializedLocalVariable)
}
}
#[cfg(test)]
mod uninitialized_local_variables_detector_tests {
use crate::detect::{
detector::IssueDetector,
low::uninitialized_local_variable::UninitializedLocalVariableDetector,
};
#[test]
fn test_uninitialized_local_variables() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/UninitializedLocalVariables.sol",
);
let mut detector = UninitializedLocalVariableDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 12);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/local_variable_shadowing.rs:
--------------------------------------------------------------------------------
```rust
use std::{collections::BTreeMap, error::Error};
use crate::ast::{ContractKind, NodeID, NodeType};
use crate::{
capture,
context::{
browser::{ExtractVariableDeclarations, GetClosestAncestorOfTypeX},
workspace::WorkspaceContext,
},
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct LocalVariableShadowingDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for LocalVariableShadowingDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for contract in context
.contract_definitions()
.into_iter()
.filter(|&c| c.kind != ContractKind::Library && !c.is_abstract)
{
let current_contract_variables = ExtractVariableDeclarations::from(contract).extracted;
let local_contract_variables = current_contract_variables
.into_iter()
.filter(|v| !v.state_variable)
.collect::<Vec<_>>();
if let Some(state_variables) =
contract.get_all_state_variables_in_linearized_base_contracts_chain(context)
{
for local_contract_variable in local_contract_variables {
if state_variables.iter().any(|v| v.name == local_contract_variable.name) {
// It's okay to allow EventDefinitions/ ErrorDefinitions to shadow the state
// variable name
if local_contract_variable
.closest_ancestor_of_type(context, NodeType::EventDefinition)
.is_some()
|| local_contract_variable
.closest_ancestor_of_type(context, NodeType::ErrorDefinition)
.is_some()
{
continue;
}
capture!(self, context, local_contract_variable);
}
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("Local Variable Shadows State Variable")
}
fn description(&self) -> String {
String::from("Rename the local variable that shadows another state variable.")
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::LocalVariableShadowing)
}
}
mod contract_hierarchy_variable_helpers {
use crate::{
ast::{ASTNode, ContractDefinition, VariableDeclaration},
context::{browser::ExtractVariableDeclarations, workspace::WorkspaceContext},
};
impl ContractDefinition {
pub fn get_all_state_variables_in_linearized_base_contracts_chain(
&self,
context: &WorkspaceContext,
) -> Option<Vec<VariableDeclaration>> {
let mut all_state_variable_ids = vec![];
for contract_id in self.linearized_base_contracts.iter() {
if let ASTNode::ContractDefinition(c) = context.nodes.get(contract_id)? {
let variable_declarations = ExtractVariableDeclarations::from(c).extracted;
all_state_variable_ids
.extend(variable_declarations.into_iter().filter(|v| v.state_variable))
}
}
Some(all_state_variable_ids)
}
}
}
#[cfg(test)]
mod local_variable_shadowing_tests {
use crate::detect::{
detector::IssueDetector, low::local_variable_shadowing::LocalVariableShadowingDetector,
};
#[test]
fn test_local_variable_shadowing() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/LocalVariableShadow.sol",
);
let mut detector = LocalVariableShadowingDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 3);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/contract_locks_ether.rs:
--------------------------------------------------------------------------------
```rust
use std::collections::BTreeMap;
use std::error::Error;
use crate::ast::NodeID;
use crate::{
capture,
context::workspace::WorkspaceContext,
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct ContractLocksEtherDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for ContractLocksEtherDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for contract in context.deployable_contracts() {
let Some(accepts_eth) = contract.can_accept_eth(context) else {
continue;
};
let Some(allows_withdraw) = contract.allows_withdrawal_of_eth(context) else {
continue;
};
if accepts_eth && !allows_withdraw {
capture!(self, context, contract);
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}
fn title(&self) -> String {
String::from("Contract locks Ether without a withdraw function")
}
fn description(&self) -> String {
String::from(
"It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, \
which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function \
that allows for the withdrawal of Ether from the contract.",
)
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
IssueDetectorNamePool::ContractLocksEther.to_string()
}
}
/// Handles tasks related to contract level analysis for eth
mod contract_eth_helper {
use crate::{
ast::{ASTNode, ContractDefinition, StateMutability},
context::{
graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
workspace::WorkspaceContext,
},
detect::helpers,
};
#[derive(Default)]
struct EthWithdrawalAllowerTracker {
has_calls_that_sends_native_eth: bool,
}
impl CallGraphVisitor for EthWithdrawalAllowerTracker {
fn visit_any(&mut self, ast_node: &ASTNode) -> eyre::Result<()> {
if !self.has_calls_that_sends_native_eth
&& helpers::has_calls_that_sends_native_eth(ast_node)
{
self.has_calls_that_sends_native_eth = true;
}
Ok(())
}
}
impl ContractDefinition {
pub(super) fn can_accept_eth(&self, context: &WorkspaceContext) -> Option<bool> {
for func in self.entrypoint_functions(context)? {
if *func.state_mutability() == StateMutability::Payable {
return Some(true);
}
}
Some(false)
}
pub(super) fn allows_withdrawal_of_eth(&self, context: &WorkspaceContext) -> Option<bool> {
for func in self.entrypoint_functions(context)? {
let callgraphs =
CallGraphConsumer::get(context, &[&func.into()], CallGraphDirection::Inward)
.ok()?;
for callgraph in callgraphs {
let mut tracker = EthWithdrawalAllowerTracker::default();
callgraph.accept(context, &mut tracker).ok()?;
if tracker.has_calls_that_sends_native_eth {
return Some(true);
}
}
}
Some(false)
}
}
}
#[cfg(test)]
mod contract_locks_ether_detector_tests {
use crate::detect::{
detector::IssueDetector, high::contract_locks_ether::ContractLocksEtherDetector,
};
#[test]
fn test_contract_locks_ether() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/ContractLocksEther.sol",
);
let mut detector = ContractLocksEtherDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 2);
}
}
```
--------------------------------------------------------------------------------
/aderyn_core/src/audit/public_functions_no_sender.rs:
--------------------------------------------------------------------------------
```rust
use prettytable::{Row, row};
use super::auditor::AuditorDetector;
use crate::{
ast::{FunctionKind, NodeType},
context::{
browser::ExtractModifierInvocations,
workspace::{ASTNode, WorkspaceContext},
},
detect::helpers::{
get_implemented_external_and_public_functions, has_msg_sender_binary_operation,
},
};
use std::{cmp::Ordering, collections::BTreeSet, error::Error};
#[derive(Clone, Eq, PartialEq)]
pub struct NoChecksInstance {
pub contract_name: String,
pub function_name: String,
pub function_kind: FunctionKind,
}
impl Ord for NoChecksInstance {
fn cmp(&self, other: &Self) -> Ordering {
let by_contract = self.contract_name.cmp(&other.contract_name);
if by_contract == Ordering::Equal {
self.function_name.cmp(&other.function_name)
} else {
by_contract
}
}
}
impl PartialOrd for NoChecksInstance {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Default)]
pub struct PublicFunctionsNoSenderChecksDetector {
// contract_name, function_name
found_instances: BTreeSet<NoChecksInstance>,
}
impl AuditorDetector for PublicFunctionsNoSenderChecksDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
let functions =
get_implemented_external_and_public_functions(context).filter(|function_definition| {
// Check if the function has an owner-related modifier
let does_not_have_an_owner_modifier =
!ExtractModifierInvocations::from(*function_definition).extracted.iter().any(
|modifier| {
modifier.modifier_name.name() == "onlyOwner"
|| modifier.modifier_name.name() == "onlyAdmin"
|| modifier.modifier_name.name() == "onlyRole"
|| modifier.modifier_name.name() == "requiresAuth"
},
);
// Check if the function has a `msg.sender` BinaryOperation check
let has_msg_sender_binary_operation =
has_msg_sender_binary_operation(&((*function_definition).into()));
// TODO Check if the function has a hasRole identifier with msg.sender as an arg
does_not_have_an_owner_modifier && !has_msg_sender_binary_operation
});
functions.for_each(|function_definition| {
if let ASTNode::ContractDefinition(contract_definition) = context
.get_closest_ancestor(function_definition.id, NodeType::ContractDefinition)
.unwrap()
{
let contract_name = contract_definition.name.clone();
self.found_instances.insert(NoChecksInstance {
contract_name,
function_name: function_definition.name.clone(),
function_kind: function_definition.kind().clone(),
});
}
});
Ok(!self.found_instances.is_empty())
}
fn title(&self) -> String {
String::from("Public and External Functions Without `msg.sender` Checks")
}
fn table_titles(&self) -> Row {
row!["Contract", "Function Kind", "Function Name"]
}
fn table_rows(&self) -> Vec<Row> {
self.found_instances
.iter()
.map(|instance| {
row![instance.contract_name, instance.function_kind, instance.function_name,]
})
.collect()
}
fn skeletal_clone(&self) -> Box<dyn AuditorDetector> {
Box::<PublicFunctionsNoSenderChecksDetector>::default()
}
}
#[cfg(test)]
mod public_functions_no_sender_checks {
use crate::{
audit::{
auditor::AuditorDetector,
public_functions_no_sender::PublicFunctionsNoSenderChecksDetector,
},
detect::test_utils::load_solidity_source_unit,
};
#[test]
fn test_public_functions_no_sender_checks() {
let context = load_solidity_source_unit(
"../tests/contract-playground/src/auditor_mode/PublicFunctionsWithoutSenderCheck.sol",
);
let mut detector = PublicFunctionsNoSenderChecksDetector::default();
let found = detector.detect(&context).unwrap();
// assert that the detector found an issue
assert!(found);
assert!(detector.found_instances.len() == 5);
}
}
```
--------------------------------------------------------------------------------
/tests/ast/modifier_definition.json:
--------------------------------------------------------------------------------
```json
{
"absolutePath": "a",
"exportedSymbols": { "C": [14] },
"id": 15,
"nodeType": "SourceUnit",
"nodes": [
{
"abstract": false,
"baseContracts": [],
"canonicalName": "C",
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"id": 14,
"linearizedBaseContracts": [14],
"name": "C",
"nameLocation": "9:1:1",
"nodeType": "ContractDefinition",
"nodes": [
{
"body": {
"id": 5,
"nodeType": "Block",
"src": "32:6:1",
"statements": [
{ "id": 4, "nodeType": "PlaceholderStatement", "src": "34:1:1" }
]
},
"id": 6,
"name": "M",
"nameLocation": "22:1:1",
"nodeType": "ModifierDefinition",
"parameters": {
"id": 3,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 2,
"mutability": "mutable",
"name": "i",
"nameLocation": "29:1:1",
"nodeType": "VariableDeclaration",
"scope": 6,
"src": "24:6:1",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
},
"typeName": {
"id": 1,
"name": "uint",
"nodeType": "ElementaryTypeName",
"src": "24:4:1",
"typeDescriptions": {
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
},
"visibility": "internal"
}
],
"src": "23:8:1"
},
"src": "13:25:1",
"virtual": false,
"visibility": "internal"
},
{
"body": {
"id": 12,
"nodeType": "Block",
"src": "64:2:1",
"statements": []
},
"functionSelector": "28811f59",
"id": 13,
"implemented": true,
"kind": "function",
"modifiers": [
{
"arguments": [
{
"hexValue": "31",
"id": 9,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "number",
"lValueRequested": false,
"nodeType": "Literal",
"src": "54:1:1",
"typeDescriptions": {
"typeIdentifier": "t_rational_1_by_1",
"typeString": "int_const 1"
},
"value": "1"
}
],
"id": 10,
"kind": "modifierInvocation",
"modifierName": {
"id": 8,
"name": "M",
"nameLocations": ["52:1:1"],
"nodeType": "IdentifierPath",
"referencedDeclaration": 6,
"src": "52:1:1"
},
"nodeType": "ModifierInvocation",
"src": "52:4:1"
},
{
"arguments": null,
"id": 10,
"kind": "modifierInvocation",
"modifierName": {
"id": 8,
"name": "M",
"nameLocations": ["52:1:1"],
"nodeType": "IdentifierPath",
"referencedDeclaration": 6,
"src": "52:1:1"
},
"nodeType": "ModifierInvocation",
"src": "52:4:1"
}
],
"name": "F",
"nameLocation": "48:1:1",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 7,
"nodeType": "ParameterList",
"parameters": [],
"src": "49:2:1"
},
"returnParameters": {
"id": 11,
"nodeType": "ParameterList",
"parameters": [],
"src": "64:0:1"
},
"scope": 14,
"src": "39:27:1",
"stateMutability": "nonpayable",
"virtual": false,
"visibility": "public"
}
],
"scope": 15,
"src": "0:68:1",
"usedErrors": []
}
],
"src": "0:69:1"
}
```