This is page 9 of 103. Use http://codebase.md/cyfrin/aderyn?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .cargo
│ └── config.toml
├── .git-blame-ignore-revs
├── .gitattributes
├── .github
│ ├── images
│ │ ├── aderyn_logo.png
│ │ ├── poweredbycyfrinblack.png
│ │ └── poweredbycyfrinblue.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── false_positive_issue.md
│ │ └── feature_request.md
│ └── workflows
│ ├── cargo.yml
│ ├── dependencies.yml
│ ├── release.yml
│ ├── reports.yml
│ └── toml.yml
├── .gitignore
├── .gitmodules
├── .vscode
│ └── settings.json
├── aderyn
│ ├── Cargo.toml
│ ├── oranda.json
│ ├── README.md
│ ├── src
│ │ ├── birdsong.rs
│ │ ├── completions.rs
│ │ ├── lib.rs
│ │ ├── lsp.rs
│ │ ├── main.rs
│ │ ├── mcp.rs
│ │ └── panic.rs
│ └── templates
│ └── aderyn.toml
├── aderyn_core
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ ├── ast
│ │ │ ├── ast_nodes.rs
│ │ │ ├── ast.rs
│ │ │ ├── impls
│ │ │ │ ├── ctx
│ │ │ │ │ ├── utils.rs
│ │ │ │ │ └── workspace.rs
│ │ │ │ ├── ctx.rs
│ │ │ │ ├── disp
│ │ │ │ │ ├── blocks.rs
│ │ │ │ │ ├── contracts.rs
│ │ │ │ │ ├── enumerations.rs
│ │ │ │ │ ├── errors.rs
│ │ │ │ │ ├── events.rs
│ │ │ │ │ ├── expressions.rs
│ │ │ │ │ ├── functions.rs
│ │ │ │ │ ├── identifiers.rs
│ │ │ │ │ ├── literals.rs
│ │ │ │ │ ├── modifiers.rs
│ │ │ │ │ ├── statements.rs
│ │ │ │ │ ├── structures.rs
│ │ │ │ │ ├── types.rs
│ │ │ │ │ ├── user_defined_value_types.rs
│ │ │ │ │ ├── using_for_directives.rs
│ │ │ │ │ └── variables.rs
│ │ │ │ ├── disp.rs
│ │ │ │ ├── node
│ │ │ │ │ ├── blocks.rs
│ │ │ │ │ ├── contracts.rs
│ │ │ │ │ ├── documentation.rs
│ │ │ │ │ ├── enumerations.rs
│ │ │ │ │ ├── errors.rs
│ │ │ │ │ ├── events.rs
│ │ │ │ │ ├── expressions.rs
│ │ │ │ │ ├── functions.rs
│ │ │ │ │ ├── identifiers.rs
│ │ │ │ │ ├── import_directives.rs
│ │ │ │ │ ├── literals.rs
│ │ │ │ │ ├── modifiers.rs
│ │ │ │ │ ├── pragma_directives.rs
│ │ │ │ │ ├── source_units.rs
│ │ │ │ │ ├── statements.rs
│ │ │ │ │ ├── structures.rs
│ │ │ │ │ ├── types.rs
│ │ │ │ │ ├── user_defined_value_types.rs
│ │ │ │ │ ├── using_for_directives.rs
│ │ │ │ │ └── variables.rs
│ │ │ │ ├── node.rs
│ │ │ │ ├── own
│ │ │ │ │ ├── hashing.rs
│ │ │ │ │ ├── node_id.rs
│ │ │ │ │ ├── source_units.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ └── own.rs
│ │ │ ├── impls.rs
│ │ │ ├── macros.rs
│ │ │ ├── magic.rs
│ │ │ ├── node_type.rs
│ │ │ └── yul.rs
│ │ ├── ast.rs
│ │ ├── audit
│ │ │ ├── attack_surface.rs
│ │ │ ├── auditor.rs
│ │ │ ├── entrypoint.rs
│ │ │ └── public_functions_no_sender.rs
│ │ ├── audit.rs
│ │ ├── context
│ │ │ ├── browser
│ │ │ │ ├── ancestral_line.rs
│ │ │ │ ├── closest_ancestor.rs
│ │ │ │ ├── external_calls.rs
│ │ │ │ ├── extractor.rs
│ │ │ │ ├── immediate_children.rs
│ │ │ │ ├── location.rs
│ │ │ │ ├── macros.rs
│ │ │ │ ├── parent.rs
│ │ │ │ ├── peek_over.rs
│ │ │ │ ├── peek_under.rs
│ │ │ │ ├── peek.rs
│ │ │ │ ├── siblings.rs
│ │ │ │ ├── sort_nodes.rs
│ │ │ │ └── storage_vars.rs
│ │ │ ├── browser.rs
│ │ │ ├── capturable.rs
│ │ │ ├── flow
│ │ │ │ ├── display.rs
│ │ │ │ ├── error.rs
│ │ │ │ ├── kind.rs
│ │ │ │ ├── primitives.rs
│ │ │ │ ├── reducibles.rs
│ │ │ │ ├── tests.rs
│ │ │ │ ├── utils.rs
│ │ │ │ ├── visualizer.rs
│ │ │ │ └── voids.rs
│ │ │ ├── flow.rs
│ │ │ ├── graph
│ │ │ │ ├── callgraph
│ │ │ │ │ ├── legacy.rs
│ │ │ │ │ ├── new.rs
│ │ │ │ │ ├── tests.rs
│ │ │ │ │ ├── utils.rs
│ │ │ │ │ └── visit.rs
│ │ │ │ ├── callgraph.rs
│ │ │ │ ├── preprocess
│ │ │ │ │ ├── legacy.rs
│ │ │ │ │ └── new.rs
│ │ │ │ ├── preprocess.rs
│ │ │ │ ├── traits.rs
│ │ │ │ └── utils.rs
│ │ │ ├── graph.rs
│ │ │ ├── macros.rs
│ │ │ ├── mcp
│ │ │ │ ├── callgraph
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── callgraph.rs
│ │ │ │ ├── contract_surface
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── util.rs
│ │ │ │ ├── contract_surface.rs
│ │ │ │ ├── list_contracts
│ │ │ │ │ ├── render.rs
│ │ │ │ │ └── tool.rs
│ │ │ │ ├── list_contracts.rs
│ │ │ │ ├── node_finder
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── node_finder.rs
│ │ │ │ ├── node_summarizer
│ │ │ │ │ ├── render.rs
│ │ │ │ │ ├── tool.rs
│ │ │ │ │ └── utils.rs
│ │ │ │ ├── node_summarizer.rs
│ │ │ │ ├── project_overview
│ │ │ │ │ ├── render.rs
│ │ │ │ │ └── tool.rs
│ │ │ │ ├── project_overview.rs
│ │ │ │ ├── tool_guide
│ │ │ │ │ └── tool.rs
│ │ │ │ └── tool_guide.rs
│ │ │ ├── mcp.rs
│ │ │ ├── router
│ │ │ │ ├── external_calls.rs
│ │ │ │ ├── internal_calls.rs
│ │ │ │ ├── modifier_calls.rs
│ │ │ │ └── tests.rs
│ │ │ ├── router.rs
│ │ │ └── workspace.rs
│ │ ├── context.rs
│ │ ├── detect
│ │ │ ├── detector.rs
│ │ │ ├── entrypoint.rs
│ │ │ ├── helpers.rs
│ │ │ ├── high
│ │ │ │ ├── _template.rs
│ │ │ │ ├── abi_encode_packed_hash_collision.rs
│ │ │ │ ├── arbitrary_transfer_from.rs
│ │ │ │ ├── const_func_changes_state.rs
│ │ │ │ ├── contract_locks_ether.rs
│ │ │ │ ├── dangerous_unary_operator.rs
│ │ │ │ ├── delegate_call_unchecked_address.rs
│ │ │ │ ├── delete_nested_mapping.rs
│ │ │ │ ├── dynamic_array_length_assignment.rs
│ │ │ │ ├── enumerable_loop_removal.rs
│ │ │ │ ├── eth_send_unchecked_address.rs
│ │ │ │ ├── experimental_encoder.rs
│ │ │ │ ├── function_selector_collision.rs
│ │ │ │ ├── incorrect_caret_operator.rs
│ │ │ │ ├── incorrect_erc20_interface.rs
│ │ │ │ ├── incorrect_erc721_interface.rs
│ │ │ │ ├── incorrect_shift_order.rs
│ │ │ │ ├── misused_boolean.rs
│ │ │ │ ├── msg_value_in_loops.rs
│ │ │ │ ├── multiple_constructors.rs
│ │ │ │ ├── nested_struct_in_mapping.rs
│ │ │ │ ├── out_of_order_retryable.rs
│ │ │ │ ├── pre_declared_variable_usage.rs
│ │ │ │ ├── reentrancy_state_change.rs
│ │ │ │ ├── reused_contract_name.rs
│ │ │ │ ├── rtlo.rs
│ │ │ │ ├── selfdestruct.rs
│ │ │ │ ├── signed_integer_storage_array.rs
│ │ │ │ ├── state_variable_shadowing.rs
│ │ │ │ ├── storage_array_memory_edit.rs
│ │ │ │ ├── strict_equality_contract_balance.rs
│ │ │ │ ├── tautological_compare.rs
│ │ │ │ ├── tautology_or_contradiction.rs
│ │ │ │ ├── tx_origin_used_for_auth.rs
│ │ │ │ ├── unchecked_low_level_call.rs
│ │ │ │ ├── unchecked_send.rs
│ │ │ │ ├── unprotected_initializer.rs
│ │ │ │ ├── unsafe_casting.rs
│ │ │ │ ├── weak_randomness.rs
│ │ │ │ └── yul_return.rs
│ │ │ ├── high.rs
│ │ │ ├── low
│ │ │ │ ├── _template.rs
│ │ │ │ ├── assert_state_change.rs
│ │ │ │ ├── block_timestamp_deadline.rs
│ │ │ │ ├── boolean_equality.rs
│ │ │ │ ├── builtin_symbol_shadowing.rs
│ │ │ │ ├── centralization_risk.rs
│ │ │ │ ├── constant_function_contains_assembly.rs
│ │ │ │ ├── costly_loop.rs
│ │ │ │ ├── dead_code.rs
│ │ │ │ ├── delegatecall_in_loop.rs
│ │ │ │ ├── deprecated_oz_function.rs
│ │ │ │ ├── division_before_multiplication.rs
│ │ │ │ ├── ecrecover.rs
│ │ │ │ ├── empty_block.rs
│ │ │ │ ├── empty_require_revert.rs
│ │ │ │ ├── function_initializing_state.rs
│ │ │ │ ├── function_pointer_in_constructor.rs
│ │ │ │ ├── inconsistent_type_names.rs
│ │ │ │ ├── incorrect_modifier.rs
│ │ │ │ ├── internal_function_used_once.rs
│ │ │ │ ├── large_numeric_literal.rs
│ │ │ │ ├── literal_instead_of_constant.rs
│ │ │ │ ├── local_variable_shadowing.rs
│ │ │ │ ├── missing_inheritance.rs
│ │ │ │ ├── modifier_used_only_once.rs
│ │ │ │ ├── multiple_placeholders.rs
│ │ │ │ ├── non_reentrant_not_first.rs
│ │ │ │ ├── push_0_opcode.rs
│ │ │ │ ├── redundant_statement.rs
│ │ │ │ ├── require_revert_in_loop.rs
│ │ │ │ ├── return_bomb.rs
│ │ │ │ ├── solmate_safe_transfer_lib.rs
│ │ │ │ ├── state_change_without_event.rs
│ │ │ │ ├── state_no_address_check.rs
│ │ │ │ ├── state_variable_could_be_constant.rs
│ │ │ │ ├── state_variable_could_be_immutable.rs
│ │ │ │ ├── state_variable_read_external.rs
│ │ │ │ ├── storage_array_length_not_cached.rs
│ │ │ │ ├── todo.rs
│ │ │ │ ├── unchecked_return.rs
│ │ │ │ ├── uninitialized_local_variable.rs
│ │ │ │ ├── unsafe_erc20_operation.rs
│ │ │ │ ├── unsafe_oz_erc721_mint.rs
│ │ │ │ ├── unspecific_solidity_pragma.rs
│ │ │ │ ├── unused_error.rs
│ │ │ │ ├── unused_import.rs
│ │ │ │ ├── unused_public_function.rs
│ │ │ │ ├── unused_state_variable.rs
│ │ │ │ └── void_constructor.rs
│ │ │ ├── low.rs
│ │ │ └── test_utils.rs
│ │ ├── detect.rs
│ │ ├── lib.rs
│ │ ├── stats
│ │ │ ├── cloc.rs
│ │ │ ├── dbg_tips.txt
│ │ │ ├── ignore.rs
│ │ │ ├── token.rs
│ │ │ └── util.rs
│ │ ├── stats.rs
│ │ ├── test_utils
│ │ │ └── load_source_unit.rs
│ │ ├── test_utils.rs
│ │ ├── visitor
│ │ │ ├── ast_visitor.rs
│ │ │ ├── macros.rs
│ │ │ └── workspace_visitor.rs
│ │ └── visitor.rs
│ ├── templates
│ │ └── mcp-tool-response
│ │ ├── callgraph.md
│ │ ├── contract_surface.md
│ │ ├── list_contracts.md
│ │ ├── node_finder_get_all.md
│ │ ├── node_finder_grep.md
│ │ ├── node_finder_search.md
│ │ ├── node_summarizer.md
│ │ ├── project_overview.md
│ │ └── tool_guide.md
│ └── tests
│ ├── common
│ │ ├── ancestral_line.rs
│ │ ├── closest_ancestor.rs
│ │ ├── immediate_children.rs
│ │ ├── immediate_parent.rs
│ │ ├── mod.rs
│ │ ├── new_ast_nodes.rs
│ │ ├── peek_over.rs
│ │ └── sibling.rs
│ └── traversal.rs
├── aderyn_driver
│ ├── .gitignore
│ ├── benches
│ │ └── detectors.rs
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ ├── compile.rs
│ │ ├── config.rs
│ │ ├── display.rs
│ │ ├── driver.rs
│ │ ├── interface
│ │ │ ├── json.rs
│ │ │ ├── lsp.rs
│ │ │ ├── markdown.rs
│ │ │ ├── mod.rs
│ │ │ ├── sarif.rs
│ │ │ ├── tables.rs
│ │ │ └── util.rs
│ │ ├── lib.rs
│ │ ├── mcp.rs
│ │ ├── process.rs
│ │ └── runner.rs
│ └── tests
│ └── astgen.rs
├── bacon.toml
├── benchmarks
│ ├── aderyn
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── iteration_times.svg
│ │ │ └── pdf.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── iteration_times_small.svg
│ │ ├── iteration_times.svg
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── relative_iteration_times_small.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── SD.svg
│ │ └── typical.svg
│ ├── arbitrary-transfer-from
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── avoid-abi-encode-packed
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── block-timestamp-deadline
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── centralization-risk
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── constants-instead-of-literals
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── delegate-call-in-loop
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── deprecated-oz-functions
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── ecrecover
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── empty-block
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── hello_world
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── inconsistent-type-names
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── large-numeric-literal
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── non-reentrant-before-others
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── push-zero-opcode
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── report
│ │ └── index.html
│ ├── require-with-string
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── solmate-safe-transfer-lib
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unindexed-events
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unprotected-initializer
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unsafe-erc20-functions
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unsafe-oz-erc721-mint
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── unspecific-solidity-pragma
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-internal-function
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-modifier
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ ├── useless-public-function
│ │ ├── base
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ ├── change
│ │ │ └── estimates.json
│ │ ├── new
│ │ │ ├── benchmark.json
│ │ │ ├── estimates.json
│ │ │ ├── sample.json
│ │ │ └── tukey.json
│ │ └── report
│ │ ├── both
│ │ │ ├── pdf.svg
│ │ │ └── regression.svg
│ │ ├── change
│ │ │ ├── mean.svg
│ │ │ ├── median.svg
│ │ │ └── t-test.svg
│ │ ├── index.html
│ │ ├── MAD.svg
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ ├── pdf_small.svg
│ │ ├── pdf.svg
│ │ ├── regression_small.svg
│ │ ├── regression.svg
│ │ ├── relative_pdf_small.svg
│ │ ├── relative_regression_small.svg
│ │ ├── SD.svg
│ │ ├── slope.svg
│ │ └── typical.svg
│ └── zero-address-check
│ ├── base
│ │ ├── benchmark.json
│ │ ├── estimates.json
│ │ ├── sample.json
│ │ └── tukey.json
│ ├── change
│ │ └── estimates.json
│ ├── new
│ │ ├── benchmark.json
│ │ ├── estimates.json
│ │ ├── sample.json
│ │ └── tukey.json
│ └── report
│ ├── both
│ │ ├── pdf.svg
│ │ └── regression.svg
│ ├── change
│ │ ├── mean.svg
│ │ ├── median.svg
│ │ └── t-test.svg
│ ├── index.html
│ ├── MAD.svg
│ ├── mean.svg
│ ├── median.svg
│ ├── pdf_small.svg
│ ├── pdf.svg
│ ├── regression_small.svg
│ ├── regression.svg
│ ├── relative_pdf_small.svg
│ ├── relative_regression_small.svg
│ ├── SD.svg
│ ├── slope.svg
│ └── typical.svg
├── Cargo.lock
├── Cargo.toml
├── cli
│ ├── benchmarks.sh
│ └── reportgen.sh
├── CODEOWNERS
├── CONTRIBUTING.md
├── cyfrinup
│ ├── dynamic_script
│ └── why.md
├── deny.toml
├── dist-workspace.toml
├── funding.json
├── LICENSE
├── Makefile
├── package-lock.json
├── package.json
├── README.md
├── RELEASE_CHECKLIST.md
├── reports
│ ├── adhoc-sol-files-highs-only-report.json
│ ├── adhoc-sol-files-report.md
│ ├── ccip-functions-report.md
│ ├── empty_report.md
│ ├── hardhat-playground-report.md
│ ├── nft-report-icm.md
│ ├── nft-report.md
│ ├── prb-math-report.md
│ ├── report.json
│ ├── report.md
│ ├── report.sarif
│ ├── sablier-aderyn-toml-nested-root.md
│ ├── templegold-report.md
│ └── uniswap_profile.md
├── rust-toolchain.toml
├── rustfmt.toml
├── tests
│ ├── adhoc-sol-files
│ │ ├── aderyn.toml
│ │ ├── Counter.sol
│ │ ├── DemoASTNodes.sol
│ │ ├── Helper.sol
│ │ ├── InconsistentUints.sol
│ │ ├── inheritance
│ │ │ ├── ExtendedInheritance.sol
│ │ │ ├── IContractInheritance.sol
│ │ │ └── InheritanceBase.sol
│ │ ├── InternalFunctions.sol
│ │ ├── lib
│ │ │ └── ThisShouldBeExcluded.sol
│ │ ├── multiple-versions
│ │ │ ├── 0.4
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.5
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.6
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ ├── 0.7
│ │ │ │ ├── A.sol
│ │ │ │ └── B.sol
│ │ │ └── 0.8
│ │ │ ├── A.sol
│ │ │ └── B.sol
│ │ ├── OnceModifierExample.sol
│ │ └── StateVariables.sol
│ ├── ast
│ │ ├── abstract_contract.json
│ │ ├── address_payable.json
│ │ ├── array_type_name.json
│ │ ├── ast-erc4626.json
│ │ ├── base_constructor_call.json
│ │ ├── bit_not.json
│ │ ├── call.json
│ │ ├── constructor.json
│ │ ├── contract_dep_order.json
│ │ ├── do_while.json
│ │ ├── documentation_1.json
│ │ ├── documentation_2.json
│ │ ├── documentation_3.json
│ │ ├── documentation_local_variable.json
│ │ ├── documentation_on_statements.json
│ │ ├── documentation_triple.json
│ │ ├── empty_block.json
│ │ ├── enum_value_declaration.json
│ │ ├── enum_value.json
│ │ ├── event_definition.json
│ │ ├── experimental_encoder_pragma.json
│ │ ├── fallback_and_reveice_ether.json
│ │ ├── fallback_payable.json
│ │ ├── fallback.json
│ │ ├── function_type.json
│ │ ├── function.json
│ │ ├── global_enum.json
│ │ ├── global_struct.json
│ │ ├── inheritance_specifier.json
│ │ ├── leave.json
│ │ ├── license.json
│ │ ├── long_type_name_binary_operation.json
│ │ ├── long_type_name_identifier.json
│ │ ├── loop.json
│ │ ├── mappings.json
│ │ ├── modifier_definition.json
│ │ ├── modifier_invocation.json
│ │ ├── mutability.json
│ │ ├── nested_functions.json
│ │ ├── non_utf8.json
│ │ ├── override.json
│ │ ├── placeholder_statement.json
│ │ ├── receive_ether.json
│ │ ├── short_type_name_ref.json
│ │ ├── short_type_name.json
│ │ ├── slot_offset.json
│ │ ├── smoke.json
│ │ ├── source_location.json
│ │ ├── string.json
│ │ ├── stringlit.json
│ │ ├── switch_default.json
│ │ ├── switch.json
│ │ ├── try_catch.json
│ │ ├── two_base_functions.json
│ │ ├── unicode.json
│ │ ├── used_errors.json
│ │ ├── userDefinedValueType.json
│ │ ├── using_for_directive.json
│ │ ├── var_access.json
│ │ └── yul_hex_literal.json
│ ├── contract-playground
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── dot
│ │ │ └── .gitkeep
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── script
│ │ │ └── Counter.s.sol
│ │ ├── src
│ │ │ ├── AbstractContract.sol
│ │ │ ├── AderynIgnoreCustomDetectors.sol
│ │ │ ├── AdminContract.sol
│ │ │ ├── ArbitraryTransferFrom.sol
│ │ │ ├── AssemblyExample.sol
│ │ │ ├── AssertStateChange.sol
│ │ │ ├── auditor_mode
│ │ │ │ ├── ExternalCalls.sol
│ │ │ │ └── PublicFunctionsWithoutSenderCheck.sol
│ │ │ ├── BooleanEquality.sol
│ │ │ ├── BuiltinSymbolShadow.sol
│ │ │ ├── CacheArrayLength.sol
│ │ │ ├── CallGraphTests.sol
│ │ │ ├── Casting.sol
│ │ │ ├── cloc
│ │ │ │ ├── AnotherHeavilyCommentedContract.sol
│ │ │ │ ├── EmptyContractFile.sol
│ │ │ │ └── HeavilyCommentedContract.sol
│ │ │ ├── CompilerBugStorageSignedIntegerArray.sol
│ │ │ ├── ConstantFuncsAssembly.sol
│ │ │ ├── ConstantsLiterals.sol
│ │ │ ├── ConstFuncChangeState.sol
│ │ │ ├── ContractLocksEther.sol
│ │ │ ├── ContractWithTodo.sol
│ │ │ ├── control_flow
│ │ │ │ └── SimpleProgram.sol
│ │ │ ├── CostlyOperationsInsideLoops.sol
│ │ │ ├── Counter.sol
│ │ │ ├── CrazyPragma.sol
│ │ │ ├── DangerousStrictEquality1.sol
│ │ │ ├── DangerousStrictEquality2.sol
│ │ │ ├── DangerousUnaryOperator.sol
│ │ │ ├── DeadCode.sol
│ │ │ ├── DelegateCallWithoutAddressCheck.sol
│ │ │ ├── DeletionNestedMappingStructureContract.sol
│ │ │ ├── DeprecatedOZFunctions.sol
│ │ │ ├── DivisionBeforeMultiplication.sol
│ │ │ ├── DynamicArrayLengthAssignment.sol
│ │ │ ├── EmitAfterExternalCall.sol
│ │ │ ├── EmptyBlocks.sol
│ │ │ ├── EnumerableSetIteration.sol
│ │ │ ├── eth2
│ │ │ │ └── DepositContract.sol
│ │ │ ├── ExperimentalEncoder.sol
│ │ │ ├── ExternalCalls.sol
│ │ │ ├── FunctionInitializingState.sol
│ │ │ ├── FunctionPointers.sol
│ │ │ ├── FunctionSignatureCollision.sol
│ │ │ ├── HugeConstants.sol
│ │ │ ├── IgnoreEverything.sol
│ │ │ ├── InconsistentUints.sol
│ │ │ ├── IncorrectCaretOperator.sol
│ │ │ ├── IncorrectERC20.sol
│ │ │ ├── IncorrectERC721.sol
│ │ │ ├── IncorrectModifier.sol
│ │ │ ├── IncorrectShift.sol
│ │ │ ├── inheritance
│ │ │ │ ├── ExtendedInheritance.sol
│ │ │ │ ├── IContractInheritance.sol
│ │ │ │ └── InheritanceBase.sol
│ │ │ ├── InternalFunctions.sol
│ │ │ ├── KeccakContract.sol
│ │ │ ├── LocalVariableShadow.sol
│ │ │ ├── MissingInheritance.sol
│ │ │ ├── MisusedBoolean.sol
│ │ │ ├── MsgValueInLoop.sol
│ │ │ ├── MultipleConstructorSchemes.sol
│ │ │ ├── MultiplePlaceholders.sol
│ │ │ ├── nested
│ │ │ │ ├── 1
│ │ │ │ │ └── Nested.sol
│ │ │ │ └── 2
│ │ │ │ └── Nested.sol
│ │ │ ├── nested_mappings
│ │ │ │ ├── LaterVersion.sol
│ │ │ │ └── NestedMappings.sol
│ │ │ ├── OnceModifierExample.sol
│ │ │ ├── OnlyLibrary.sol
│ │ │ ├── OutOfOrderRetryable.sol
│ │ │ ├── parent_chain
│ │ │ │ └── ParentChainContract.sol
│ │ │ ├── PragmaRange.sol
│ │ │ ├── PreDeclaredVarUsage.sol
│ │ │ ├── PublicFunction.sol
│ │ │ ├── PublicVariableReadInExternalContext.sol
│ │ │ ├── RedundantStatements.sol
│ │ │ ├── ReturnBomb.sol
│ │ │ ├── reused_contract_name
│ │ │ │ ├── ContractA.sol
│ │ │ │ └── ContractB.sol
│ │ │ ├── RevertsAndRequriesInLoops.sol
│ │ │ ├── router
│ │ │ │ ├── ExternalCalls.sol
│ │ │ │ ├── FallbackAndReceiveOverrides.sol
│ │ │ │ ├── InternalCalls.sol
│ │ │ │ ├── ModifierCalls.sol
│ │ │ │ └── VarOverridesFunction.sol
│ │ │ ├── RTLO.sol
│ │ │ ├── SendEtherNoChecks.sol
│ │ │ ├── SendEtherNoChecksLibImport.sol
│ │ │ ├── StateChangeAfterExternalCall.sol
│ │ │ ├── StateShadowing.sol
│ │ │ ├── StateVariableCouldBeDeclaredConstant.sol
│ │ │ ├── StateVariableCouldBeDeclaredImmutable.sol
│ │ │ ├── StateVariables.sol
│ │ │ ├── StateVariablesChangesWithoutEvents.sol
│ │ │ ├── StateVariablesManipulation.sol
│ │ │ ├── StorageConditionals.sol
│ │ │ ├── StorageParameters.sol
│ │ │ ├── T11sTranferer.sol
│ │ │ ├── TautologicalCompare.sol
│ │ │ ├── TautologyOrContradiction.sol
│ │ │ ├── TestERC20.sol
│ │ │ ├── TransientKeyword.sol
│ │ │ ├── Trump.sol
│ │ │ ├── TxOriginUsedForAuth.sol
│ │ │ ├── U2.sol
│ │ │ ├── U3.sol
│ │ │ ├── U4.sol
│ │ │ ├── U5.sol
│ │ │ ├── UncheckedCalls.sol
│ │ │ ├── UncheckedReturn.sol
│ │ │ ├── UncheckedSend.sol
│ │ │ ├── UninitializedLocalVariables.sol
│ │ │ ├── UninitializedStateVariable.sol
│ │ │ ├── uniswap
│ │ │ │ ├── UniswapV2Swapper.sol
│ │ │ │ └── UniswapV3Swapper.sol
│ │ │ ├── UnprotectedInitialize.sol
│ │ │ ├── UnsafeERC721Mint.sol
│ │ │ ├── UnusedError.sol
│ │ │ ├── UnusedImport.sol
│ │ │ ├── UnusedStateVariables.sol
│ │ │ ├── UsingSelfdestruct.sol
│ │ │ ├── VoidConstructor.sol
│ │ │ ├── WeakRandomness.sol
│ │ │ ├── WrongOrderOfLayout.sol
│ │ │ ├── YulReturn.sol
│ │ │ └── ZeroAddressCheck.sol
│ │ └── test
│ │ └── Counter.t.sol
│ ├── foundry-nft-f23
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── foundry.lock
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── remappings.txt
│ │ └── src
│ │ ├── BasicNft.sol
│ │ ├── F1.sol
│ │ ├── F2.sol
│ │ ├── Initializer.sol
│ │ └── inner-core-modules
│ │ └── ICM.sol
│ ├── foundry-nft-f23-icm
│ │ ├── .github
│ │ │ └── workflows
│ │ │ └── test.yml
│ │ ├── .gitignore
│ │ ├── aderyn.toml
│ │ ├── foundry.toml
│ │ ├── README.md
│ │ ├── remappings.txt
│ │ └── src
│ │ ├── BasicNft.sol
│ │ ├── F1.sol
│ │ ├── F2.sol
│ │ ├── Initializer.sol
│ │ └── inner-core-modules
│ │ └── ICM.sol
│ ├── hardhat-js-playground
│ │ ├── .gitignore
│ │ ├── artifacts
│ │ │ ├── build-info
│ │ │ │ └── cee6fe9a9a2f03f7ff10a27ab2746af6.json
│ │ │ └── contracts
│ │ │ ├── Counter.sol
│ │ │ │ ├── Counter.dbg.json
│ │ │ │ └── Counter.json
│ │ │ ├── ExtendedInheritance.sol
│ │ │ │ ├── ExtendedInheritance.dbg.json
│ │ │ │ └── ExtendedInheritance.json
│ │ │ ├── IContractInheritance.sol
│ │ │ │ ├── IContractInheritance.dbg.json
│ │ │ │ └── IContractInheritance.json
│ │ │ ├── InheritanceBase.sol
│ │ │ │ ├── InheritanceBase.dbg.json
│ │ │ │ └── InheritanceBase.json
│ │ │ ├── KeccakContract.sol
│ │ │ │ ├── KeccakContract.dbg.json
│ │ │ │ └── KeccakContract.json
│ │ │ ├── Lock.sol
│ │ │ │ ├── Lock.dbg.json
│ │ │ │ └── Lock.json
│ │ │ └── StateVariables.sol
│ │ │ ├── StateVariables.dbg.json
│ │ │ └── StateVariables.json
│ │ ├── contracts
│ │ │ ├── Counter.sol
│ │ │ ├── ExtendedInheritance.sol
│ │ │ ├── IContractInheritance.sol
│ │ │ ├── InheritanceBase.sol
│ │ │ ├── KeccakContract.sol
│ │ │ ├── Lock.sol
│ │ │ └── StateVariables.sol
│ │ ├── hardhat.config.js
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── scripts
│ │ │ └── deploy.js
│ │ ├── test
│ │ │ └── Lock.js
│ │ └── yarn.lock
│ ├── no-sol-files
│ │ ├── extra
│ │ │ └── HelloAgain.md
│ │ ├── Hello.txt
│ │ └── Hello.yul
│ └── toml
│ ├── nested_project1
│ │ ├── aderyn.toml
│ │ ├── folder1
│ │ │ └── hardhat.config.ts
│ │ ├── folder2
│ │ │ └── hardhat.config.ts
│ │ └── folder3
│ │ └── file.txt
│ └── nested_project2
│ ├── aderyn.toml
│ ├── folder1
│ │ └── foundry.toml
│ └── folder2
│ └── file1.txt
├── tools
│ └── xtask
│ ├── Cargo.toml
│ └── src
│ ├── blesspr.rs
│ ├── cut_release.rs
│ ├── flags.rs
│ ├── main.rs
│ ├── reportgen.rs
│ └── tomlgen.rs
└── typos.toml
```
# Files
--------------------------------------------------------------------------------
/aderyn/src/mcp.rs:
--------------------------------------------------------------------------------
```rust
1 | use aderyn_driver::{SingletonMcpServer, driver};
2 | use indoc::indoc;
3 |
4 | use rmcp::{
5 | ServiceExt,
6 | transport::{
7 | StreamableHttpService, streamable_http_server::session::local::LocalSessionManager,
8 | },
9 | };
10 | use tokio::runtime::Builder;
11 |
12 | /// Starts an MCP server with streamable HTTP transport on given port
13 | pub fn spin_up_http_stream_mcp_server(args: driver::Args, port: u16) {
14 | let mcp_server = driver::create_mcp_server(args).unwrap_or_else(|| {
15 | eprintln!("Unable to generate workspace context. Likely, issue compiling solidity files.");
16 | std::process::exit(1);
17 | });
18 |
19 | let async_runtime = Builder::new_multi_thread()
20 | .worker_threads(20)
21 | .thread_name("aderyn-http-mcp-server-async-runtime")
22 | .thread_stack_size(3 * 1024 * 1024)
23 | .enable_all()
24 | .build()
25 | .expect("unable to start async runtime");
26 |
27 | eprintln!(
28 | indoc! {"
29 | Dear human,
30 |
31 | The MCP Server is now running at:
32 |
33 | http://127.0.0.1:{}/mcp
34 |
35 | You can connect using any MCP-compatible client—such as an editor, an AI agent,
36 | or any other tool that supports the protocol.
37 |
38 | If you'd simply like to explore the available tools, you can use the free MCP
39 | Inspector by running:
40 |
41 | npx -y @modelcontextprotocol/inspector
42 |
43 | in another terminal, and then enter the server URL shown above.
44 |
45 | ⚠️ Live reload is disabled to keep session data consistent.
46 | Restart the MCP server whenever you need to apply changes from updated files.
47 | "},
48 | port
49 | );
50 |
51 | async_runtime.block_on(async move {
52 | let mcp_server = SingletonMcpServer::new(mcp_server);
53 |
54 | let service = StreamableHttpService::new(
55 | move || Ok(mcp_server.clone()),
56 | LocalSessionManager::default().into(),
57 | Default::default(),
58 | );
59 |
60 | let router = axum::Router::new().nest_service("/mcp", service);
61 |
62 | let tcp_listener =
63 | tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)).await.unwrap();
64 |
65 | let _ = axum::serve(tcp_listener, router).with_graceful_shutdown(shutdown_signal()).await;
66 | });
67 |
68 | // dbg!(args.input_config);
69 | }
70 |
71 | pub fn spin_up_stdio_mcp_server(args: driver::Args) {
72 | let mcp_server = driver::create_mcp_server(args).unwrap_or_else(|| {
73 | eprintln!("Unable to generate workspace context. Likely, issue compiling solidity files.");
74 | std::process::exit(1);
75 | });
76 |
77 | let async_runtime = Builder::new_multi_thread()
78 | .worker_threads(20)
79 | .thread_name("aderyn-stdio-mcp-async-runtime")
80 | .thread_stack_size(3 * 1024 * 1024)
81 | .enable_all()
82 | .build()
83 | .expect("unable to start async runtime");
84 |
85 | eprintln!(indoc! {"
86 | Dear human,
87 |
88 | The MCP Server is now running on STDIO.
89 |
90 | You can connect using any MCP-compatible client—such as an editor, an AI agent,
91 | or any other tool that supports the protocol.
92 |
93 | If you'd simply like to explore the available tools, you can use the free MCP
94 | Inspector by running:
95 |
96 | npx -y @modelcontextprotocol/inspector
97 |
98 | ⚠️ Live reload is disabled to keep session data consistent.
99 | Restart the MCP server whenever you need to apply changes from updated files.
100 | "});
101 |
102 | let stdio = || (tokio::io::stdin(), tokio::io::stdout());
103 |
104 | async_runtime.block_on(async move {
105 | let mcp_server = SingletonMcpServer::new(mcp_server);
106 |
107 | let service = mcp_server
108 | .serve(stdio())
109 | .await
110 | .inspect_err(|e| {
111 | eprintln!("serving error: {:?}", e);
112 | })
113 | .expect("stdout");
114 |
115 | service.waiting().await.expect("failed to start mcp server on stdio");
116 | });
117 | }
118 |
119 | async fn shutdown_signal() {
120 | let ctrl_c = async {
121 | tokio::signal::ctrl_c().await.expect("failed to install Ctrl+C handler");
122 | };
123 |
124 | #[cfg(unix)]
125 | let terminate = async {
126 | use tokio::signal::unix::{SignalKind, signal};
127 | let mut term = signal(SignalKind::terminate()).expect("failed to install SIGTERM handler");
128 | term.recv().await;
129 | };
130 |
131 | tokio::select! {
132 | _ = ctrl_c => {},
133 | _ = terminate => {},
134 | }
135 |
136 | eprintln!("Signal received, shutting down gracefully...");
137 | std::process::exit(0);
138 | }
139 |
```
--------------------------------------------------------------------------------
/tools/xtask/src/cut_release.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{io::BufRead, time::Duration};
2 |
3 | use xshell::{Shell, cmd};
4 |
5 | use crate::flags::CutRelease;
6 |
7 | pub fn cut_release(cut_release: CutRelease) -> anyhow::Result<()> {
8 | let sh = Shell::new()?;
9 | sh.change_dir(env!("CARGO_MANIFEST_DIR"));
10 | sh.change_dir("../../");
11 |
12 | // Wait for existing release completion
13 | wait_for_release_completion(&sh)?;
14 |
15 | // Sanity checks and syncs
16 | sync_tags(&sh)?;
17 | perform_prechecks(&sh)?;
18 |
19 | // Release process
20 | dry_run(&sh, &cut_release)?;
21 | kick_off_release(&sh, &cut_release)?;
22 |
23 | // Wait for release completion
24 | println!("Waiting for release completion...");
25 | std::thread::sleep(Duration::from_secs(10 * 60));
26 | wait_for_release_completion(&sh)?;
27 |
28 | // Regenerate sarif report (it would be broken because version number is contained)
29 | regenerate_sarif_report(&sh)?;
30 | Ok(())
31 | }
32 |
33 | fn wait_for_release_completion(sh: &Shell) -> anyhow::Result<()> {
34 | let poll_time = Duration::from_secs(12);
35 |
36 | // Check if actions are still pending
37 | let actions_completed = {
38 | let cmd = cmd!(sh, "gh run list --workflow release.yml");
39 | let x = cmd.output()?.stdout.to_vec();
40 | let stdout = String::from_utf8_lossy(&x);
41 | stdout.lines().filter(|l| !l.is_empty()).all(|l| l.starts_with("completed"))
42 | };
43 |
44 | if !actions_completed {
45 | println!(
46 | "A release or a release plan is in progress ... [next poll: {}s]",
47 | poll_time.as_secs()
48 | );
49 | std::thread::sleep(Duration::from_secs(12));
50 | wait_for_release_completion(sh)?;
51 | return Ok(());
52 | }
53 |
54 | Ok(())
55 | }
56 |
57 | fn kick_off_release(sh: &Shell, cut_release: &CutRelease) -> anyhow::Result<()> {
58 | let execute_cmd = if cut_release.patch {
59 | cmd!(sh, "cargo release patch --no-publish --exclude xtask --execute")
60 | } else if cut_release.minor {
61 | cmd!(sh, "cargo release minor --no-publish --exclude xtask --execute")
62 | } else {
63 | unreachable!()
64 | };
65 |
66 | println!("Kick off the release process\n\taderyn\n?[y/N]");
67 | let mut line = String::new();
68 | let stdin = std::io::stdin();
69 | stdin.lock().read_line(&mut line).unwrap();
70 |
71 | if line.contains("y") {
72 | println!("Kicked-off release process!");
73 | let d = execute_cmd.stdin(line.clone());
74 | d.run()?;
75 | } else {
76 | println!("Declined release process!");
77 | }
78 |
79 | Ok(())
80 | }
81 |
82 | fn dry_run(sh: &Shell, cut_release: &CutRelease) -> anyhow::Result<()> {
83 | let dry_run_command = if cut_release.patch {
84 | cmd!(sh, "cargo release patch --no-publish --exclude xtask --no-tag")
85 | } else if cut_release.minor {
86 | cmd!(sh, "cargo release minor --no-publish --exclude xtask --no-tag")
87 | } else {
88 | unreachable!();
89 | };
90 | dry_run_command.run()?;
91 | Ok(())
92 | }
93 |
94 | fn sync_tags(sh: &Shell) -> anyhow::Result<()> {
95 | let sync = cmd!(sh, "git fetch --prune-tags origin");
96 | sync.run()?;
97 | Ok(())
98 | }
99 |
100 | fn regenerate_sarif_report(sh: &Shell) -> anyhow::Result<()> {
101 | let regen = cmd!(sh, "cargo blesspr");
102 | regen.run()?;
103 | Ok(())
104 | }
105 |
106 | fn perform_prechecks(sh: &Shell) -> anyhow::Result<()> {
107 | // Exit if not on dev branch
108 | let curr_branch = {
109 | let cmd = cmd!(sh, "git rev-parse --abbrev-ref HEAD");
110 | let output = cmd.output()?;
111 | String::from_utf8(output.stdout)?
112 | };
113 | if curr_branch.trim() != "dev" {
114 | eprintln!("Please switch to dev branch and retry!. Curr branch: {}", curr_branch.trim());
115 | std::process::exit(1);
116 | }
117 |
118 | // Error out if there are untracked files/staging changes
119 | let uncommitted_stuff = {
120 | let cmd = cmd!(sh, "git status --porcelain");
121 | let output = cmd.output()?;
122 | String::from_utf8(output.stdout)?
123 | };
124 | if !uncommitted_stuff.is_empty() {
125 | eprintln!("Please clear your staging area and retry!");
126 | std::process::exit(1);
127 | }
128 |
129 | // Error if dev branch is not in sync with remote
130 | let local_commit_hash = {
131 | let cmd = cmd!(sh, "git rev-parse dev");
132 | let output = cmd.output()?;
133 | String::from_utf8(output.stdout)?
134 | };
135 | let remote_commit_hash = {
136 | let cmd = cmd!(sh, "git rev-parse origin/dev");
137 | let output = cmd.output()?;
138 | String::from_utf8(output.stdout)?
139 | };
140 | if remote_commit_hash != local_commit_hash {
141 | eprintln!("dev branch is not in sync with origin. Do that and retry!");
142 | std::process::exit(1);
143 | }
144 | Ok(())
145 | }
146 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/uninitialized_local_variable.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{
2 | collections::{BTreeMap, HashSet},
3 | error::Error,
4 | };
5 |
6 | use crate::ast::{ASTNode, NodeID};
7 |
8 | use crate::{
9 | capture,
10 | context::{browser::ExtractReferencedDeclarations, workspace::WorkspaceContext},
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct UninitializedLocalVariableDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for UninitializedLocalVariableDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | // Assumption:
25 | // VariableDeclarationStatements consists of statements that look like `uint x;` `uint y,
26 | // z;`, `uint p = 12;` but are not declared at the contract level (state level) but
27 | // rather within functions and modifiers
28 |
29 | let mut potentially_uninitialized_local_variables = HashSet::new();
30 |
31 | for variable_declaration_statement in context
32 | .variable_declaration_statements()
33 | .into_iter()
34 | .filter(|s| s.initial_value.is_none())
35 | {
36 | potentially_uninitialized_local_variables.extend(
37 | variable_declaration_statement.declarations.iter().flat_map(|s| {
38 | if let Some(s) = s {
39 | return Some(s.id);
40 | }
41 | None
42 | }),
43 | );
44 | }
45 |
46 | // We can filter out the initialized variables by looking at LHS of assignments.
47 | // This trick works for local variables because it's not possible to have structs, mappings,
48 | // dynamic arrays declared local to the function.
49 | for assignment in context.assignments() {
50 | let references =
51 | ExtractReferencedDeclarations::from(assignment.left_hand_side.as_ref()).extracted;
52 | potentially_uninitialized_local_variables.retain(|v| !references.contains(v));
53 | }
54 |
55 | // Blacklist variables assigned via Yul Assignments
56 | let mut blacklist_variable_names = HashSet::new();
57 |
58 | for yul_assignment in context.yul_assignments() {
59 | blacklist_variable_names
60 | .extend(yul_assignment.variable_names.iter().map(|v| v.name.clone()))
61 | }
62 |
63 | for id in potentially_uninitialized_local_variables {
64 | if let Some(ASTNode::VariableDeclaration(v)) = context.nodes.get(&id)
65 | && !blacklist_variable_names.contains(&v.name)
66 | {
67 | // Ignore memory structs because they can have an initializeMethod of their own.
68 | // So not covered under the assignment operator
69 | if v.type_descriptions
70 | .type_string
71 | .as_ref()
72 | .is_some_and(|type_string| !type_string.contains("struct "))
73 | {
74 | capture!(self, context, v);
75 | }
76 | }
77 | }
78 |
79 | Ok(!self.found_instances.is_empty())
80 | }
81 |
82 | fn severity(&self) -> IssueSeverity {
83 | IssueSeverity::Low
84 | }
85 |
86 | fn title(&self) -> String {
87 | String::from("Uninitialized Local Variable")
88 | }
89 |
90 | fn description(&self) -> String {
91 | String::from(
92 | "Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero to improve code readability.",
93 | )
94 | }
95 |
96 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
97 | self.found_instances.clone()
98 | }
99 |
100 | fn name(&self) -> String {
101 | format!("{}", IssueDetectorNamePool::UninitializedLocalVariable)
102 | }
103 | }
104 |
105 | #[cfg(test)]
106 | mod uninitialized_local_variables_detector_tests {
107 |
108 | use crate::detect::{
109 | detector::IssueDetector,
110 | low::uninitialized_local_variable::UninitializedLocalVariableDetector,
111 | };
112 |
113 | #[test]
114 |
115 | fn test_uninitialized_local_variables() {
116 | let context = crate::detect::test_utils::load_solidity_source_unit(
117 | "../tests/contract-playground/src/UninitializedLocalVariables.sol",
118 | );
119 |
120 | let mut detector = UninitializedLocalVariableDetector::default();
121 | let found = detector.detect(&context).unwrap();
122 |
123 | assert!(found);
124 | assert_eq!(detector.instances().len(), 12);
125 | }
126 | }
127 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/local_variable_shadowing.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{ContractKind, NodeID, NodeType};
4 |
5 | use crate::{
6 | capture,
7 | context::{
8 | browser::{ExtractVariableDeclarations, GetClosestAncestorOfTypeX},
9 | workspace::WorkspaceContext,
10 | },
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct LocalVariableShadowingDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for LocalVariableShadowingDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | for contract in context
25 | .contract_definitions()
26 | .into_iter()
27 | .filter(|&c| c.kind != ContractKind::Library && !c.is_abstract)
28 | {
29 | let current_contract_variables = ExtractVariableDeclarations::from(contract).extracted;
30 | let local_contract_variables = current_contract_variables
31 | .into_iter()
32 | .filter(|v| !v.state_variable)
33 | .collect::<Vec<_>>();
34 | if let Some(state_variables) =
35 | contract.get_all_state_variables_in_linearized_base_contracts_chain(context)
36 | {
37 | for local_contract_variable in local_contract_variables {
38 | if state_variables.iter().any(|v| v.name == local_contract_variable.name) {
39 | // It's okay to allow EventDefinitions/ ErrorDefinitions to shadow the state
40 | // variable name
41 | if local_contract_variable
42 | .closest_ancestor_of_type(context, NodeType::EventDefinition)
43 | .is_some()
44 | || local_contract_variable
45 | .closest_ancestor_of_type(context, NodeType::ErrorDefinition)
46 | .is_some()
47 | {
48 | continue;
49 | }
50 |
51 | capture!(self, context, local_contract_variable);
52 | }
53 | }
54 | }
55 | }
56 |
57 | Ok(!self.found_instances.is_empty())
58 | }
59 |
60 | fn severity(&self) -> IssueSeverity {
61 | IssueSeverity::Low
62 | }
63 |
64 | fn title(&self) -> String {
65 | String::from("Local Variable Shadows State Variable")
66 | }
67 |
68 | fn description(&self) -> String {
69 | String::from("Rename the local variable that shadows another state variable.")
70 | }
71 |
72 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
73 | self.found_instances.clone()
74 | }
75 |
76 | fn name(&self) -> String {
77 | format!("{}", IssueDetectorNamePool::LocalVariableShadowing)
78 | }
79 | }
80 |
81 | mod contract_hierarchy_variable_helpers {
82 | use crate::{
83 | ast::{ASTNode, ContractDefinition, VariableDeclaration},
84 | context::{browser::ExtractVariableDeclarations, workspace::WorkspaceContext},
85 | };
86 |
87 | impl ContractDefinition {
88 | pub fn get_all_state_variables_in_linearized_base_contracts_chain(
89 | &self,
90 | context: &WorkspaceContext,
91 | ) -> Option<Vec<VariableDeclaration>> {
92 | let mut all_state_variable_ids = vec![];
93 | for contract_id in self.linearized_base_contracts.iter() {
94 | if let ASTNode::ContractDefinition(c) = context.nodes.get(contract_id)? {
95 | let variable_declarations = ExtractVariableDeclarations::from(c).extracted;
96 | all_state_variable_ids
97 | .extend(variable_declarations.into_iter().filter(|v| v.state_variable))
98 | }
99 | }
100 | Some(all_state_variable_ids)
101 | }
102 | }
103 | }
104 |
105 | #[cfg(test)]
106 | mod local_variable_shadowing_tests {
107 |
108 | use crate::detect::{
109 | detector::IssueDetector, low::local_variable_shadowing::LocalVariableShadowingDetector,
110 | };
111 |
112 | #[test]
113 |
114 | fn test_local_variable_shadowing() {
115 | let context = crate::detect::test_utils::load_solidity_source_unit(
116 | "../tests/contract-playground/src/LocalVariableShadow.sol",
117 | );
118 |
119 | let mut detector = LocalVariableShadowingDetector::default();
120 | let found = detector.detect(&context).unwrap();
121 | assert!(found);
122 |
123 | assert_eq!(detector.instances().len(), 3);
124 | }
125 | }
126 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/contract_locks_ether.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::collections::BTreeMap;
2 |
3 | use std::error::Error;
4 |
5 | use crate::ast::NodeID;
6 |
7 | use crate::{
8 | capture,
9 | context::workspace::WorkspaceContext,
10 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
11 | };
12 |
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct ContractLocksEtherDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for ContractLocksEtherDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | for contract in context.deployable_contracts() {
25 | let Some(accepts_eth) = contract.can_accept_eth(context) else {
26 | continue;
27 | };
28 | let Some(allows_withdraw) = contract.allows_withdrawal_of_eth(context) else {
29 | continue;
30 | };
31 | if accepts_eth && !allows_withdraw {
32 | capture!(self, context, contract);
33 | }
34 | }
35 | Ok(!self.found_instances.is_empty())
36 | }
37 |
38 | fn severity(&self) -> IssueSeverity {
39 | IssueSeverity::High
40 | }
41 |
42 | fn title(&self) -> String {
43 | String::from("Contract locks Ether without a withdraw function")
44 | }
45 |
46 | fn description(&self) -> String {
47 | String::from(
48 | "It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, \
49 | which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function \
50 | that allows for the withdrawal of Ether from the contract.",
51 | )
52 | }
53 |
54 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
55 | self.found_instances.clone()
56 | }
57 |
58 | fn name(&self) -> String {
59 | IssueDetectorNamePool::ContractLocksEther.to_string()
60 | }
61 | }
62 |
63 | /// Handles tasks related to contract level analysis for eth
64 | mod contract_eth_helper {
65 | use crate::{
66 | ast::{ASTNode, ContractDefinition, StateMutability},
67 | context::{
68 | graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
69 | workspace::WorkspaceContext,
70 | },
71 | detect::helpers,
72 | };
73 |
74 | #[derive(Default)]
75 | struct EthWithdrawalAllowerTracker {
76 | has_calls_that_sends_native_eth: bool,
77 | }
78 |
79 | impl CallGraphVisitor for EthWithdrawalAllowerTracker {
80 | fn visit_any(&mut self, ast_node: &ASTNode) -> eyre::Result<()> {
81 | if !self.has_calls_that_sends_native_eth
82 | && helpers::has_calls_that_sends_native_eth(ast_node)
83 | {
84 | self.has_calls_that_sends_native_eth = true;
85 | }
86 | Ok(())
87 | }
88 | }
89 |
90 | impl ContractDefinition {
91 | pub(super) fn can_accept_eth(&self, context: &WorkspaceContext) -> Option<bool> {
92 | for func in self.entrypoint_functions(context)? {
93 | if *func.state_mutability() == StateMutability::Payable {
94 | return Some(true);
95 | }
96 | }
97 | Some(false)
98 | }
99 |
100 | pub(super) fn allows_withdrawal_of_eth(&self, context: &WorkspaceContext) -> Option<bool> {
101 | for func in self.entrypoint_functions(context)? {
102 | let callgraphs =
103 | CallGraphConsumer::get(context, &[&func.into()], CallGraphDirection::Inward)
104 | .ok()?;
105 | for callgraph in callgraphs {
106 | let mut tracker = EthWithdrawalAllowerTracker::default();
107 | callgraph.accept(context, &mut tracker).ok()?;
108 |
109 | if tracker.has_calls_that_sends_native_eth {
110 | return Some(true);
111 | }
112 | }
113 | }
114 | Some(false)
115 | }
116 | }
117 | }
118 |
119 | #[cfg(test)]
120 | mod contract_locks_ether_detector_tests {
121 |
122 | use crate::detect::{
123 | detector::IssueDetector, high::contract_locks_ether::ContractLocksEtherDetector,
124 | };
125 |
126 | #[test]
127 |
128 | fn test_contract_locks_ether() {
129 | let context = crate::detect::test_utils::load_solidity_source_unit(
130 | "../tests/contract-playground/src/ContractLocksEther.sol",
131 | );
132 |
133 | let mut detector = ContractLocksEtherDetector::default();
134 | let found = detector.detect(&context).unwrap();
135 |
136 | assert!(found);
137 | assert_eq!(detector.instances().len(), 2);
138 | }
139 | }
140 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/audit/public_functions_no_sender.rs:
--------------------------------------------------------------------------------
```rust
1 | use prettytable::{Row, row};
2 |
3 | use super::auditor::AuditorDetector;
4 | use crate::{
5 | ast::{FunctionKind, NodeType},
6 | context::{
7 | browser::ExtractModifierInvocations,
8 | workspace::{ASTNode, WorkspaceContext},
9 | },
10 | detect::helpers::{
11 | get_implemented_external_and_public_functions, has_msg_sender_binary_operation,
12 | },
13 | };
14 | use std::{cmp::Ordering, collections::BTreeSet, error::Error};
15 |
16 | #[derive(Clone, Eq, PartialEq)]
17 | pub struct NoChecksInstance {
18 | pub contract_name: String,
19 | pub function_name: String,
20 | pub function_kind: FunctionKind,
21 | }
22 |
23 | impl Ord for NoChecksInstance {
24 | fn cmp(&self, other: &Self) -> Ordering {
25 | let by_contract = self.contract_name.cmp(&other.contract_name);
26 | if by_contract == Ordering::Equal {
27 | self.function_name.cmp(&other.function_name)
28 | } else {
29 | by_contract
30 | }
31 | }
32 | }
33 |
34 | impl PartialOrd for NoChecksInstance {
35 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
36 | Some(self.cmp(other))
37 | }
38 | }
39 |
40 | #[derive(Default)]
41 | pub struct PublicFunctionsNoSenderChecksDetector {
42 | // contract_name, function_name
43 | found_instances: BTreeSet<NoChecksInstance>,
44 | }
45 |
46 | impl AuditorDetector for PublicFunctionsNoSenderChecksDetector {
47 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
48 | let functions =
49 | get_implemented_external_and_public_functions(context).filter(|function_definition| {
50 | // Check if the function has an owner-related modifier
51 | let does_not_have_an_owner_modifier =
52 | !ExtractModifierInvocations::from(*function_definition).extracted.iter().any(
53 | |modifier| {
54 | modifier.modifier_name.name() == "onlyOwner"
55 | || modifier.modifier_name.name() == "onlyAdmin"
56 | || modifier.modifier_name.name() == "onlyRole"
57 | || modifier.modifier_name.name() == "requiresAuth"
58 | },
59 | );
60 | // Check if the function has a `msg.sender` BinaryOperation check
61 | let has_msg_sender_binary_operation =
62 | has_msg_sender_binary_operation(&((*function_definition).into()));
63 | // TODO Check if the function has a hasRole identifier with msg.sender as an arg
64 | does_not_have_an_owner_modifier && !has_msg_sender_binary_operation
65 | });
66 |
67 | functions.for_each(|function_definition| {
68 | if let ASTNode::ContractDefinition(contract_definition) = context
69 | .get_closest_ancestor(function_definition.id, NodeType::ContractDefinition)
70 | .unwrap()
71 | {
72 | let contract_name = contract_definition.name.clone();
73 | self.found_instances.insert(NoChecksInstance {
74 | contract_name,
75 | function_name: function_definition.name.clone(),
76 | function_kind: function_definition.kind().clone(),
77 | });
78 | }
79 | });
80 |
81 | Ok(!self.found_instances.is_empty())
82 | }
83 |
84 | fn title(&self) -> String {
85 | String::from("Public and External Functions Without `msg.sender` Checks")
86 | }
87 |
88 | fn table_titles(&self) -> Row {
89 | row!["Contract", "Function Kind", "Function Name"]
90 | }
91 |
92 | fn table_rows(&self) -> Vec<Row> {
93 | self.found_instances
94 | .iter()
95 | .map(|instance| {
96 | row![instance.contract_name, instance.function_kind, instance.function_name,]
97 | })
98 | .collect()
99 | }
100 |
101 | fn skeletal_clone(&self) -> Box<dyn AuditorDetector> {
102 | Box::<PublicFunctionsNoSenderChecksDetector>::default()
103 | }
104 | }
105 |
106 | #[cfg(test)]
107 | mod public_functions_no_sender_checks {
108 | use crate::{
109 | audit::{
110 | auditor::AuditorDetector,
111 | public_functions_no_sender::PublicFunctionsNoSenderChecksDetector,
112 | },
113 | detect::test_utils::load_solidity_source_unit,
114 | };
115 |
116 | #[test]
117 | fn test_public_functions_no_sender_checks() {
118 | let context = load_solidity_source_unit(
119 | "../tests/contract-playground/src/auditor_mode/PublicFunctionsWithoutSenderCheck.sol",
120 | );
121 |
122 | let mut detector = PublicFunctionsNoSenderChecksDetector::default();
123 | let found = detector.detect(&context).unwrap();
124 | // assert that the detector found an issue
125 | assert!(found);
126 | assert!(detector.found_instances.len() == 5);
127 | }
128 | }
129 |
```
--------------------------------------------------------------------------------
/tests/ast/modifier_definition.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "absolutePath": "a",
3 | "exportedSymbols": { "C": [14] },
4 | "id": 15,
5 | "nodeType": "SourceUnit",
6 | "nodes": [
7 | {
8 | "abstract": false,
9 | "baseContracts": [],
10 | "canonicalName": "C",
11 | "contractDependencies": [],
12 | "contractKind": "contract",
13 | "fullyImplemented": true,
14 | "id": 14,
15 | "linearizedBaseContracts": [14],
16 | "name": "C",
17 | "nameLocation": "9:1:1",
18 | "nodeType": "ContractDefinition",
19 | "nodes": [
20 | {
21 | "body": {
22 | "id": 5,
23 | "nodeType": "Block",
24 | "src": "32:6:1",
25 | "statements": [
26 | { "id": 4, "nodeType": "PlaceholderStatement", "src": "34:1:1" }
27 | ]
28 | },
29 | "id": 6,
30 | "name": "M",
31 | "nameLocation": "22:1:1",
32 | "nodeType": "ModifierDefinition",
33 | "parameters": {
34 | "id": 3,
35 | "nodeType": "ParameterList",
36 | "parameters": [
37 | {
38 | "constant": false,
39 | "id": 2,
40 | "mutability": "mutable",
41 | "name": "i",
42 | "nameLocation": "29:1:1",
43 | "nodeType": "VariableDeclaration",
44 | "scope": 6,
45 | "src": "24:6:1",
46 | "stateVariable": false,
47 | "storageLocation": "default",
48 | "typeDescriptions": {
49 | "typeIdentifier": "t_uint256",
50 | "typeString": "uint256"
51 | },
52 | "typeName": {
53 | "id": 1,
54 | "name": "uint",
55 | "nodeType": "ElementaryTypeName",
56 | "src": "24:4:1",
57 | "typeDescriptions": {
58 | "typeIdentifier": "t_uint256",
59 | "typeString": "uint256"
60 | }
61 | },
62 | "visibility": "internal"
63 | }
64 | ],
65 | "src": "23:8:1"
66 | },
67 | "src": "13:25:1",
68 | "virtual": false,
69 | "visibility": "internal"
70 | },
71 | {
72 | "body": {
73 | "id": 12,
74 | "nodeType": "Block",
75 | "src": "64:2:1",
76 | "statements": []
77 | },
78 | "functionSelector": "28811f59",
79 | "id": 13,
80 | "implemented": true,
81 | "kind": "function",
82 | "modifiers": [
83 | {
84 | "arguments": [
85 | {
86 | "hexValue": "31",
87 | "id": 9,
88 | "isConstant": false,
89 | "isLValue": false,
90 | "isPure": true,
91 | "kind": "number",
92 | "lValueRequested": false,
93 | "nodeType": "Literal",
94 | "src": "54:1:1",
95 | "typeDescriptions": {
96 | "typeIdentifier": "t_rational_1_by_1",
97 | "typeString": "int_const 1"
98 | },
99 | "value": "1"
100 | }
101 | ],
102 | "id": 10,
103 | "kind": "modifierInvocation",
104 | "modifierName": {
105 | "id": 8,
106 | "name": "M",
107 | "nameLocations": ["52:1:1"],
108 | "nodeType": "IdentifierPath",
109 | "referencedDeclaration": 6,
110 | "src": "52:1:1"
111 | },
112 | "nodeType": "ModifierInvocation",
113 | "src": "52:4:1"
114 | },
115 | {
116 | "arguments": null,
117 | "id": 10,
118 | "kind": "modifierInvocation",
119 | "modifierName": {
120 | "id": 8,
121 | "name": "M",
122 | "nameLocations": ["52:1:1"],
123 | "nodeType": "IdentifierPath",
124 | "referencedDeclaration": 6,
125 | "src": "52:1:1"
126 | },
127 | "nodeType": "ModifierInvocation",
128 | "src": "52:4:1"
129 | }
130 | ],
131 | "name": "F",
132 | "nameLocation": "48:1:1",
133 | "nodeType": "FunctionDefinition",
134 | "parameters": {
135 | "id": 7,
136 | "nodeType": "ParameterList",
137 | "parameters": [],
138 | "src": "49:2:1"
139 | },
140 | "returnParameters": {
141 | "id": 11,
142 | "nodeType": "ParameterList",
143 | "parameters": [],
144 | "src": "64:0:1"
145 | },
146 | "scope": 14,
147 | "src": "39:27:1",
148 | "stateMutability": "nonpayable",
149 | "virtual": false,
150 | "visibility": "public"
151 | }
152 | ],
153 | "scope": 15,
154 | "src": "0:68:1",
155 | "usedErrors": []
156 | }
157 | ],
158 | "src": "0:69:1"
159 | }
160 |
```
--------------------------------------------------------------------------------
/benchmarks/push-zero-opcode/report/relative_regression_small.svg:
--------------------------------------------------------------------------------
```
1 | <svg width="450" height="300" viewBox="0 0 450 300" xmlns="http://www.w3.org/2000/svg">
2 | <text x="15" y="130" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000" transform="rotate(270, 15, 130)">
3 | Total sample time (ms)
4 | </text>
5 | <text x="255" y="285" dy="-0.5ex" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
6 | Iterations (x 10^3)
7 | </text>
8 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="244" x2="75" y2="15"/>
9 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="156" y1="244" x2="156" y2="15"/>
10 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="238" y1="244" x2="238" y2="15"/>
11 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="319" y1="244" x2="319" y2="15"/>
12 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="401" y1="244" x2="401" y2="15"/>
13 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="244" x2="434" y2="244"/>
14 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="203" x2="434" y2="203"/>
15 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="162" x2="434" y2="162"/>
16 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="120" x2="434" y2="120"/>
17 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="79" x2="434" y2="79"/>
18 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="37" x2="434" y2="37"/>
19 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="74,15 74,244 "/>
20 | <text x="65" y="244" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
21 | 0.0
22 | </text>
23 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,244 74,244 "/>
24 | <text x="65" y="203" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
25 | 20.0
26 | </text>
27 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,203 74,203 "/>
28 | <text x="65" y="162" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
29 | 40.0
30 | </text>
31 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,162 74,162 "/>
32 | <text x="65" y="120" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
33 | 60.0
34 | </text>
35 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,120 74,120 "/>
36 | <text x="65" y="79" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
37 | 80.0
38 | </text>
39 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,79 74,79 "/>
40 | <text x="65" y="37" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
41 | 100.0
42 | </text>
43 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,37 74,37 "/>
44 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="75,245 434,245 "/>
45 | <text x="75" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
46 | 0
47 | </text>
48 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="75,245 75,250 "/>
49 | <text x="156" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
50 | 0.5
51 | </text>
52 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="156,245 156,250 "/>
53 | <text x="238" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
54 | 1
55 | </text>
56 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="238,245 238,250 "/>
57 | <text x="319" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
58 | 1.5
59 | </text>
60 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="319,245 319,250 "/>
61 | <text x="401" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
62 | 2
63 | </text>
64 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="401,245 401,250 "/>
65 | <polyline fill="none" opacity="1" stroke="#E31A1C" stroke-width="1" points="75,244 434,15 "/>
66 | <polygon opacity="0.25" fill="#E31A1C" points="75,244 434,23 434,15 "/>
67 | <polyline fill="none" opacity="1" stroke="#1F78B4" stroke-width="1" points="75,244 434,51 "/>
68 | <polygon opacity="0.25" fill="#1F78B4" points="75,244 434,56 434,45 "/>
69 | </svg>
70 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/mcp/callgraph/utils.rs:
--------------------------------------------------------------------------------
```rust
1 | use super::render::*;
2 | use crate::{
3 | ast::{ASTNode, ContractDefinition, FunctionDefinition, NodeID},
4 | context::{graph::RawCallGraph, workspace::WorkspaceContext},
5 | };
6 | use rmcp::ErrorData as McpError;
7 | use std::collections::{HashSet, hash_map::Entry};
8 |
9 | pub fn build_post_order_nodes(
10 | context: &WorkspaceContext,
11 | subgraph: &RawCallGraph,
12 | entrypoint: &FunctionDefinition,
13 | ) -> Result<Vec<NodeData>, McpError> {
14 | let mut visited: HashSet<NodeID> = Default::default();
15 | let mut post_order_nodes = vec![];
16 |
17 | fn post_order_traverse(
18 | context: &WorkspaceContext,
19 | node_id: NodeID,
20 | post_order_nodes: &mut Vec<NodeData>,
21 | visited: &mut HashSet<NodeID>,
22 | subgraph: &RawCallGraph,
23 | ) {
24 | if visited.contains(&node_id) {
25 | return;
26 | }
27 | visited.insert(node_id);
28 |
29 | if let Some(to_node_ids) = subgraph.get(&node_id) {
30 | for to_id in to_node_ids {
31 | post_order_traverse(context, *to_id, post_order_nodes, visited, subgraph);
32 | }
33 | }
34 |
35 | let name = match context.nodes.get(&node_id) {
36 | Some(ASTNode::FunctionDefinition(func)) => Some(func.name.clone()),
37 | Some(ASTNode::ModifierDefinition(modifier)) => Some(modifier.name.clone()),
38 | _ => None,
39 | };
40 |
41 | let Some(name) = name else {
42 | return;
43 | };
44 |
45 | let mut called_nodes = vec![];
46 | if let Some(called) = subgraph.get(&node_id) {
47 | for c in called {
48 | let c_name = match context.nodes.get(c) {
49 | Some(ASTNode::FunctionDefinition(func)) => Some(func.name.clone()),
50 | Some(ASTNode::ModifierDefinition(modifier)) => Some(modifier.name.clone()),
51 | _ => None,
52 | };
53 |
54 | let Some(c_name) = c_name else {
55 | continue;
56 | };
57 |
58 | if let Ok(c_data) = NodeDataBuilder::default()
59 | .node_id(*c)
60 | .name(c_name)
61 | .called_nodes(vec![]) // When displaying we only show one level deep.
62 | .build()
63 | {
64 | called_nodes.push(c_data);
65 | }
66 | }
67 | }
68 |
69 | if let Ok(node_data) = NodeDataBuilder::default()
70 | .node_id(node_id)
71 | .name(name)
72 | .called_nodes(called_nodes)
73 | .build()
74 | {
75 | post_order_nodes.push(node_data);
76 | }
77 | }
78 |
79 | post_order_traverse(context, entrypoint.id, &mut post_order_nodes, &mut visited, subgraph);
80 |
81 | Ok(post_order_nodes)
82 | }
83 |
84 | pub fn build_raw_callgraph_for_entrypoint(
85 | context: &WorkspaceContext,
86 | contract: &ContractDefinition,
87 | entrypoint: &FunctionDefinition,
88 | ) -> Result<RawCallGraph, McpError> {
89 | let Some(callgraphs) = &context.callgraphs else {
90 | return Err(McpError::internal_error("callgraphs could not be formed", None));
91 | };
92 | let Some(callgraph) = callgraphs.inward_callgraphs.get(&contract.id) else {
93 | return Err(McpError::internal_error(
94 | format!("callgraph for {} doesn't exist.", contract.id),
95 | None,
96 | ));
97 | };
98 |
99 | let mut visited: HashSet<NodeID> = Default::default();
100 | let mut subgraph = RawCallGraph::default();
101 |
102 | let mut worklist = vec![entrypoint.id];
103 |
104 | while let Some(node_id) = worklist.pop() {
105 | if visited.contains(&node_id) {
106 | continue;
107 | }
108 | visited.insert(node_id);
109 | create_node_if_not_exists(node_id, &mut subgraph);
110 |
111 | if let Some(to_nodes) = callgraph.get(&node_id) {
112 | for to_id in to_nodes {
113 | create_node_if_not_exists(*to_id, &mut subgraph);
114 | create_connection_if_not_exists(node_id, *to_id, &mut subgraph);
115 | // Only explore further if it hasn't already been explored.
116 | if !visited.contains(to_id) {
117 | worklist.push(*to_id);
118 | }
119 | }
120 | }
121 | }
122 | Ok(subgraph)
123 | }
124 |
125 | #[inline(always)]
126 | fn create_node_if_not_exists(node_id: NodeID, raw_callgraph: &mut RawCallGraph) {
127 | if let Entry::Vacant(v) = raw_callgraph.entry(node_id) {
128 | v.insert(vec![]);
129 | }
130 | }
131 |
132 | #[inline]
133 | fn create_connection_if_not_exists(
134 | from_id: NodeID,
135 | to_id: NodeID,
136 | raw_callgraph: &mut RawCallGraph,
137 | ) {
138 | match raw_callgraph.entry(from_id) {
139 | Entry::Occupied(mut o) => {
140 | if !o.get().contains(&to_id) {
141 | o.get_mut().push(to_id);
142 | }
143 | }
144 | Entry::Vacant(v) => {
145 | v.insert(vec![to_id]);
146 | }
147 | }
148 | }
149 |
```
--------------------------------------------------------------------------------
/tests/hardhat-js-playground/artifacts/contracts/Counter.sol/Counter.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "_format": "hh-sol-artifact-1",
3 | "contractName": "Counter",
4 | "sourceName": "contracts/Counter.sol",
5 | "abi": [
6 | {
7 | "inputs": [],
8 | "name": "TWO",
9 | "outputs": [
10 | {
11 | "internalType": "uint256",
12 | "name": "",
13 | "type": "uint256"
14 | }
15 | ],
16 | "stateMutability": "view",
17 | "type": "function"
18 | },
19 | {
20 | "inputs": [],
21 | "name": "callIncrement",
22 | "outputs": [],
23 | "stateMutability": "nonpayable",
24 | "type": "function"
25 | },
26 | {
27 | "inputs": [],
28 | "name": "increment",
29 | "outputs": [],
30 | "stateMutability": "nonpayable",
31 | "type": "function"
32 | },
33 | {
34 | "inputs": [],
35 | "name": "incrementByTwoConstant",
36 | "outputs": [],
37 | "stateMutability": "nonpayable",
38 | "type": "function"
39 | },
40 | {
41 | "inputs": [],
42 | "name": "incrementByTwoMagic",
43 | "outputs": [],
44 | "stateMutability": "nonpayable",
45 | "type": "function"
46 | },
47 | {
48 | "inputs": [],
49 | "name": "number",
50 | "outputs": [
51 | {
52 | "internalType": "uint256",
53 | "name": "",
54 | "type": "uint256"
55 | }
56 | ],
57 | "stateMutability": "view",
58 | "type": "function"
59 | },
60 | {
61 | "inputs": [
62 | {
63 | "internalType": "uint256",
64 | "name": "newNumber",
65 | "type": "uint256"
66 | }
67 | ],
68 | "name": "setNumber",
69 | "outputs": [],
70 | "stateMutability": "nonpayable",
71 | "type": "function"
72 | }
73 | ],
74 | "bytecode": "0x60806040526000805534801561001457600080fd5b506102e3806100246000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063452707c11161005b578063452707c1146100c65780637b74087e146100d05780638381f58a146100da578063d09de08a146100f85761007d565b806334945b3a1461008257806338cd26de146100a05780633fb5c1cb146100aa575b600080fd5b61008a610102565b6040516100979190610189565b60405180910390f35b6100a8610107565b005b6100c460048036038101906100bf91906101d5565b610111565b005b6100ce61011b565b005b6100d8610136565b005b6100e2610151565b6040516100ef9190610189565b60405180910390f35b610100610157565b005b600281565b61010f610157565b565b8060008190555050565b600260008082825461012d9190610231565b92505081905550565b60026000808282546101489190610231565b92505081905550565b60005481565b60008081548092919061016990610265565b9190505550565b6000819050919050565b61018381610170565b82525050565b600060208201905061019e600083018461017a565b92915050565b600080fd5b6101b281610170565b81146101bd57600080fd5b50565b6000813590506101cf816101a9565b92915050565b6000602082840312156101eb576101ea6101a4565b5b60006101f9848285016101c0565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061023c82610170565b915061024783610170565b925082820190508082111561025f5761025e610202565b5b92915050565b600061027082610170565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036102a2576102a1610202565b5b60018201905091905056fea26469706673582212209d9bfe8376073e0b0e1f0123ffbc5bae37ce516cbfbc41f9816933997a6d327f64736f6c63430008140033",
75 | "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c8063452707c11161005b578063452707c1146100c65780637b74087e146100d05780638381f58a146100da578063d09de08a146100f85761007d565b806334945b3a1461008257806338cd26de146100a05780633fb5c1cb146100aa575b600080fd5b61008a610102565b6040516100979190610189565b60405180910390f35b6100a8610107565b005b6100c460048036038101906100bf91906101d5565b610111565b005b6100ce61011b565b005b6100d8610136565b005b6100e2610151565b6040516100ef9190610189565b60405180910390f35b610100610157565b005b600281565b61010f610157565b565b8060008190555050565b600260008082825461012d9190610231565b92505081905550565b60026000808282546101489190610231565b92505081905550565b60005481565b60008081548092919061016990610265565b9190505550565b6000819050919050565b61018381610170565b82525050565b600060208201905061019e600083018461017a565b92915050565b600080fd5b6101b281610170565b81146101bd57600080fd5b50565b6000813590506101cf816101a9565b92915050565b6000602082840312156101eb576101ea6101a4565b5b60006101f9848285016101c0565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061023c82610170565b915061024783610170565b925082820190508082111561025f5761025e610202565b5b92915050565b600061027082610170565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036102a2576102a1610202565b5b60018201905091905056fea26469706673582212209d9bfe8376073e0b0e1f0123ffbc5bae37ce516cbfbc41f9816933997a6d327f64736f6c63430008140033",
76 | "linkReferences": {},
77 | "deployedLinkReferences": {}
78 | }
79 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/unprotected_initializer.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::NodeID,
5 | capture,
6 | context::{
7 | browser::{ExtractIdentifiers, ExtractModifierInvocations, ExtractRevertStatements},
8 | graph::CallGraphVisitor,
9 | workspace::WorkspaceContext,
10 | },
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct UnprotectedInitializerDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | }
21 |
22 | impl IssueDetector for UnprotectedInitializerDetector {
23 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
24 | #[derive(Default, Debug)]
25 | struct UnprotectedInitializationTracker {
26 | has_require_or_revert: bool,
27 | has_initializer_modifier: bool, // devtooligan's suggestion
28 | }
29 |
30 | impl CallGraphVisitor for UnprotectedInitializationTracker {
31 | fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> {
32 | // Check for revert(), require(), revert SomeError()
33 | let has_req_or_revert_calls = ExtractIdentifiers::from(node)
34 | .extracted
35 | .into_iter()
36 | .any(|x| x.name == "require" || x.name == "revert");
37 |
38 | let has_revert_stmnts = !ExtractRevertStatements::from(node).extracted.is_empty();
39 |
40 | if has_req_or_revert_calls || has_revert_stmnts {
41 | self.has_require_or_revert = true;
42 | }
43 |
44 | // Check if modifier name is "initializer" or "reinitializer" and assume it works
45 | // This is done because often times initialized comes from openzeppelin and it is
46 | // out of scope when running aderyn due to it being a library
47 |
48 | let modifier_invocations = ExtractModifierInvocations::from(node).extracted;
49 |
50 | for inv in modifier_invocations {
51 | match inv.modifier_name {
52 | crate::ast::IdentifierOrIdentifierPath::Identifier(n) => {
53 | if n.name == "initializer" || n.name == "reinitializer" {
54 | self.has_initializer_modifier = true;
55 | }
56 | }
57 | crate::ast::IdentifierOrIdentifierPath::IdentifierPath(n) => {
58 | if n.name == "initializer" || n.name == "reinitializer" {
59 | self.has_initializer_modifier = true;
60 | }
61 | }
62 | }
63 | }
64 |
65 | Ok(())
66 | }
67 | }
68 |
69 | for (func, callgraphs) in context.entrypoints_with_callgraphs() {
70 | for callgraph in callgraphs {
71 | let mut tracker = UnprotectedInitializationTracker::default();
72 | callgraph.accept(context, &mut tracker)?;
73 |
74 | if func.name.starts_with("_init") || func.name.starts_with("init") {
75 | if tracker.has_initializer_modifier || tracker.has_require_or_revert {
76 | continue;
77 | }
78 | capture!(self, context, func);
79 | }
80 | }
81 | }
82 |
83 | Ok(!self.found_instances.is_empty())
84 | }
85 |
86 | fn title(&self) -> String {
87 | String::from("Unprotected initializer")
88 | }
89 |
90 | fn description(&self) -> String {
91 | String::from("Consider protecting the initializer functions with modifiers.")
92 | }
93 |
94 | fn severity(&self) -> IssueSeverity {
95 | IssueSeverity::High
96 | }
97 |
98 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
99 | self.found_instances.clone()
100 | }
101 |
102 | fn name(&self) -> String {
103 | format!("{}", IssueDetectorNamePool::UnprotectedInitializer)
104 | }
105 | }
106 |
107 | #[cfg(test)]
108 | mod unprotected_initializer_tests {
109 |
110 | use crate::detect::detector::IssueDetector;
111 |
112 | use super::UnprotectedInitializerDetector;
113 |
114 | #[test]
115 |
116 | fn test_unprotected_initializer_by_loading_contract_directly() {
117 | let context = crate::detect::test_utils::load_solidity_source_unit(
118 | "../tests/contract-playground/src/UnprotectedInitialize.sol",
119 | );
120 |
121 | let mut detector = UnprotectedInitializerDetector::default();
122 | let found = detector.detect(&context).unwrap();
123 | assert!(found);
124 | assert_eq!(detector.instances().len(), 2); // Now there are two instances: one in
125 | // InitializedContract and one in
126 | // ReinitializerContract
127 | }
128 | }
129 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/function_initializing_state.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::ast::{ASTNode, Expression, FunctionCall, Identifier, NodeID};
4 |
5 | use crate::{
6 | capture,
7 | context::{
8 | browser::ExtractReferencedDeclarations,
9 | graph::{CallGraphConsumer, CallGraphDirection, CallGraphVisitor},
10 | workspace::WorkspaceContext,
11 | },
12 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
13 | };
14 | use eyre::Result;
15 |
16 | #[derive(Default)]
17 | pub struct FunctionInitializingStateDetector {
18 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
19 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
20 | found_instances: BTreeMap<(String, usize, String), NodeID>,
21 | }
22 |
23 | impl IssueDetector for FunctionInitializingStateDetector {
24 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
25 | // PLAN
26 | // Capture state variables that are initialized directly by calling a non constant function.
27 | // Go thorough state variable declarations with initial value (this will be true when value
28 | // is set outside constructor) See if the function references non-constant state
29 | // variables. If it does, then capture it
30 |
31 | for variable_declaration in
32 | context.variable_declarations().into_iter().filter(|v| v.state_variable)
33 | {
34 | if let Some(Expression::FunctionCall(FunctionCall { expression, .. })) =
35 | variable_declaration.value.as_ref()
36 | && let Expression::Identifier(Identifier {
37 | referenced_declaration: Some(func_id),
38 | ..
39 | }) = expression.as_ref()
40 | && let Some(ASTNode::FunctionDefinition(func)) = context.nodes.get(func_id)
41 | {
42 | let callgraphs =
43 | CallGraphConsumer::get(context, &[&(func.into())], CallGraphDirection::Inward)?;
44 |
45 | for callgraph in callgraphs {
46 | let mut tracker =
47 | NonConstantStateVariableReferenceDeclarationTracker::new(context);
48 |
49 | callgraph.accept(context, &mut tracker)?;
50 |
51 | if tracker.makes_a_reference {
52 | capture!(self, context, variable_declaration);
53 | }
54 | }
55 | }
56 | }
57 |
58 | Ok(!self.found_instances.is_empty())
59 | }
60 |
61 | fn severity(&self) -> IssueSeverity {
62 | IssueSeverity::Low
63 | }
64 |
65 | fn title(&self) -> String {
66 | String::from("Function Used to Initialize State Variable")
67 | }
68 |
69 | fn description(&self) -> String {
70 | String::from(
71 | "Instead of using a function to initialize a state variable in its declaration; declare the state variable and initialize it in the constructor.",
72 | )
73 | }
74 |
75 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
76 | self.found_instances.clone()
77 | }
78 |
79 | fn name(&self) -> String {
80 | format!("{}", IssueDetectorNamePool::FunctionInitializingState)
81 | }
82 | }
83 |
84 | struct NonConstantStateVariableReferenceDeclarationTracker<'a> {
85 | makes_a_reference: bool,
86 | context: &'a WorkspaceContext,
87 | }
88 |
89 | impl<'a> NonConstantStateVariableReferenceDeclarationTracker<'a> {
90 | fn new(context: &'a WorkspaceContext) -> Self {
91 | Self { makes_a_reference: false, context }
92 | }
93 | }
94 |
95 | impl CallGraphVisitor for NonConstantStateVariableReferenceDeclarationTracker<'_> {
96 | fn visit_any(&mut self, node: &ASTNode) -> eyre::Result<()> {
97 | // We already know the condition is satisfied
98 | if self.makes_a_reference {
99 | return Ok(());
100 | }
101 |
102 | let references = ExtractReferencedDeclarations::from(node).extracted;
103 |
104 | for reference in references {
105 | if let Some(ASTNode::VariableDeclaration(variable_declaration)) =
106 | self.context.nodes.get(&reference)
107 | && variable_declaration.state_variable
108 | && !variable_declaration.constant
109 | {
110 | self.makes_a_reference = true;
111 | }
112 | }
113 |
114 | Ok(())
115 | }
116 | }
117 |
118 | #[cfg(test)]
119 | mod function_initializing_state_tests {
120 |
121 | use crate::detect::{
122 | detector::IssueDetector,
123 | low::function_initializing_state::FunctionInitializingStateDetector,
124 | };
125 |
126 | #[test]
127 |
128 | fn test_function_initializing_state() {
129 | let context = crate::detect::test_utils::load_solidity_source_unit(
130 | "../tests/contract-playground/src/FunctionInitializingState.sol",
131 | );
132 |
133 | let mut detector = FunctionInitializingStateDetector::default();
134 | let found = detector.detect(&context).unwrap();
135 | assert!(found);
136 | assert_eq!(detector.instances().len(), 3);
137 | }
138 | }
139 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low.rs:
--------------------------------------------------------------------------------
```rust
1 | pub(crate) mod assert_state_change;
2 | pub(crate) mod block_timestamp_deadline;
3 | pub(crate) mod boolean_equality;
4 | pub(crate) mod builtin_symbol_shadowing;
5 | pub(crate) mod centralization_risk;
6 | pub(crate) mod constant_function_contains_assembly;
7 | pub(crate) mod costly_loop;
8 | pub(crate) mod dead_code;
9 | pub(crate) mod delegatecall_in_loop;
10 | pub(crate) mod deprecated_oz_function;
11 | pub(crate) mod division_before_multiplication;
12 | pub(crate) mod ecrecover;
13 | pub(crate) mod empty_block;
14 | pub(crate) mod empty_require_revert;
15 | pub(crate) mod function_initializing_state;
16 | pub(crate) mod function_pointer_in_constructor;
17 | pub(crate) mod inconsistent_type_names;
18 | pub(crate) mod incorrect_modifier;
19 | pub(crate) mod internal_function_used_once;
20 | pub(crate) mod large_numeric_literal;
21 | pub(crate) mod literal_instead_of_constant;
22 | pub(crate) mod local_variable_shadowing;
23 | pub(crate) mod missing_inheritance;
24 | pub(crate) mod modifier_used_only_once;
25 | pub(crate) mod multiple_placeholders;
26 | pub(crate) mod non_reentrant_not_first;
27 | pub(crate) mod push_0_opcode;
28 | pub(crate) mod redundant_statement;
29 | pub(crate) mod require_revert_in_loop;
30 | pub(crate) mod return_bomb;
31 | pub(crate) mod solmate_safe_transfer_lib;
32 | pub(crate) mod state_change_without_event;
33 | pub(crate) mod state_no_address_check;
34 | pub(crate) mod state_variable_could_be_constant;
35 | pub(crate) mod state_variable_could_be_immutable;
36 | pub(crate) mod state_variable_read_external;
37 | pub(crate) mod storage_array_length_not_cached;
38 | pub(crate) mod todo;
39 | pub(crate) mod unchecked_return;
40 | pub(crate) mod uninitialized_local_variable;
41 | pub(crate) mod unsafe_erc20_operation;
42 | pub(crate) mod unsafe_oz_erc721_mint;
43 | pub(crate) mod unspecific_solidity_pragma;
44 | pub(crate) mod unused_error;
45 | pub(crate) mod unused_import;
46 | pub(crate) mod unused_public_function;
47 | pub(crate) mod unused_state_variable;
48 | pub(crate) mod void_constructor;
49 |
50 | pub use assert_state_change::AssertStateChangeDetector;
51 | pub use block_timestamp_deadline::BlockTimestampDeadlineDetector;
52 | pub use boolean_equality::BooleanEqualityDetector;
53 | pub use builtin_symbol_shadowing::BuiltinSymbolShadowingDetector;
54 | pub use centralization_risk::CentralizationRiskDetector;
55 | pub use constant_function_contains_assembly::ConstantFunctionContainsAssemblyDetector;
56 | pub use costly_loop::CostlyLoopDetector;
57 | pub use dead_code::DeadCodeDetector;
58 | pub use delegatecall_in_loop::DelegatecallInLoopDetector;
59 | pub use deprecated_oz_function::DeprecatedOZFunctionDetector;
60 | pub use division_before_multiplication::DivisionBeforeMultiplicationDetector;
61 | pub use ecrecover::EcrecoverDetector;
62 | pub use empty_block::EmptyBlockDetector;
63 | pub use empty_require_revert::EmptyRequireRevertDetector;
64 | pub use function_initializing_state::FunctionInitializingStateDetector;
65 | pub use function_pointer_in_constructor::FunctionPointerInConstructorDetector;
66 | pub use inconsistent_type_names::InconsistentTypeNamesDetector;
67 | pub use incorrect_modifier::IncorrectUseOfModifierDetector;
68 | pub use internal_function_used_once::InternalFunctionUsedOnceDetector;
69 | pub use large_numeric_literal::LargeLiteralValueDetector;
70 | pub use literal_instead_of_constant::LiteralsInsteadOfConstantsDetector;
71 | pub use local_variable_shadowing::LocalVariableShadowingDetector;
72 | pub use missing_inheritance::MissingInheritanceDetector;
73 | pub use modifier_used_only_once::ModifierUsedOnlyOnceDetector;
74 | pub use multiple_placeholders::MultiplePlaceholdersDetector;
75 | pub use non_reentrant_not_first::NonReentrantBeforeOthersDetector;
76 | pub use push_0_opcode::PushZeroOpcodeDetector;
77 | pub use redundant_statement::RedundantStatementDetector;
78 | pub use require_revert_in_loop::RequireRevertInLoopDetector;
79 | pub use return_bomb::ReturnBombDetector;
80 | pub use solmate_safe_transfer_lib::SolmateSafeTransferLibDetector;
81 | pub use state_change_without_event::StateVariableChangesWithoutEventDetector;
82 | pub use state_no_address_check::StateNoAddressCheckDetector;
83 | pub use state_variable_could_be_constant::StateVariableCouldBeConstantDetector;
84 | pub use state_variable_could_be_immutable::StateVariableCouldBeImmutableDetector;
85 | pub use state_variable_read_external::StateVariableReadExternalDetector;
86 | pub use storage_array_length_not_cached::CacheArrayLengthDetector;
87 | pub use todo::TodoDetector;
88 | pub use unchecked_return::UncheckedReturnDetector;
89 | pub use uninitialized_local_variable::UninitializedLocalVariableDetector;
90 | pub use unsafe_erc20_operation::UnsafeERC20OperationDetector;
91 | pub use unsafe_oz_erc721_mint::UnsafeERC721MintDetector;
92 | pub use unspecific_solidity_pragma::UnspecificSolidityPragmaDetector;
93 | pub use unused_error::UnusedErrorDetector;
94 | pub use unused_import::UnusedImportDetector;
95 | pub use unused_public_function::UnusedPublicFunctionDetector;
96 | pub use unused_state_variable::UnusedStateVariablesDetector;
97 | pub use void_constructor::VoidConstructorDetector;
98 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/macros.rs:
--------------------------------------------------------------------------------
```rust
1 | macro_rules! generate_capturable_methods {
2 | ($( $name:ident ),* $(,)*) => {
3 |
4 | #[derive(Clone)]
5 | pub enum Capturable {
6 | $($name($name),)*
7 | YulFunctionCall(YulFunctionCall),
8 | YulIdentifier(YulIdentifier),
9 | YulLiteral(YulLiteral),
10 | ASTNode(ASTNode),
11 | }
12 |
13 | $(
14 | impl From<$name> for Capturable {
15 | fn from(value: $name) -> Self {
16 | Self::$name(value)
17 | }
18 | }
19 |
20 | impl From<&$name> for Capturable {
21 | fn from(value: &$name) -> Self {
22 | Self::$name(value.clone())
23 | }
24 | }
25 | )*
26 |
27 | impl From<ASTNode> for Capturable {
28 | fn from(value: ASTNode) -> Self {
29 | Self::ASTNode(value)
30 | }
31 | }
32 |
33 | impl From<&ASTNode> for Capturable {
34 | fn from(value: &ASTNode) -> Self {
35 | Self::ASTNode(value.clone())
36 | }
37 | }
38 |
39 |
40 | impl Capturable {
41 | pub fn make_key(&self, context: &WorkspaceContext) -> (String, usize, String) {
42 | match self {
43 | Self::ASTNode(node) => context.get_node_sort_key(node),
44 | Self::YulFunctionCall(n) => context.get_node_sort_key(&n.into()),
45 | Self::YulIdentifier(n) => context.get_node_sort_key(&n.into()),
46 | Self::YulLiteral(n) => context.get_node_sort_key(&n.into()),
47 | $(Self::$name(n) => context.get_node_sort_key(&n.into()),)*
48 | }
49 | }
50 | pub fn id(&self) -> Option<NodeID> {
51 | match self {
52 | Self::ASTNode(ast_node) => ast_node.id(),
53 | Self::YulFunctionCall(_) => None,
54 | Self::YulIdentifier(_) => None,
55 | Self::YulLiteral(_) => None,
56 | $(Self::$name(n) => Some(n.id),)*
57 | }
58 | }
59 | }
60 |
61 |
62 | };
63 | }
64 |
65 | macro_rules! generate_get_source_unit {
66 |
67 | ($( $name:ident => $storage_var:ident ),* $(,)*) => {
68 |
69 | impl WorkspaceContext {
70 | pub fn get_source_unit_from_child_node(&self, node: &ASTNode) -> Option<&SourceUnit> {
71 | let source_unit_id = match node {
72 | ASTNode::SourceUnit(node) => Some(node.id),
73 | $(
74 | ASTNode::$name(node) => self
75 | .$storage_var
76 | .get(node)
77 | .map(|context| context.source_unit_id),
78 | )*
79 | };
80 | // iterate through self.source_units until the source unit with the id matching `source_unit_id` is found, then return its `absolute_path`
81 | source_unit_id.and_then(|id| {
82 | self.source_units_context
83 | .iter()
84 | .find(|source_unit| source_unit.id == id)
85 | })
86 | }
87 | }
88 |
89 | };
90 |
91 | }
92 |
93 | macro_rules! make_route {
94 | ($tool:ty, $st:tt) => {{
95 | let t = <$tool>::new(std::sync::Arc::clone(&$st));
96 | rmcp::handler::server::tool::ToolRoute::new(
97 | rmcp::model::Tool::new(
98 | t.name().to_string(),
99 | t.description().to_string(),
100 | rmcp::handler::server::tool::cached_schema_for_type::<
101 | <$tool as crate::context::mcp::ModelContextProtocolTool>::Input,
102 | >(),
103 | ),
104 | move |a| t.execute(a),
105 | )
106 | }};
107 | }
108 |
109 | macro_rules! mcp_success {
110 | ($resp:expr_2021) => {{
111 | use askama::Template;
112 | use serde::Serialize;
113 |
114 | let t = $resp;
115 |
116 | fn assert_traits<T: Serialize + Template + ?Sized>(_t: &T) {}
117 |
118 | // Enforce that $resp implements Serialize
119 | assert_traits(&t);
120 |
121 | // Serialize and render
122 | let json_value = serde_json::to_value(&t).expect("failed to serialize structured content");
123 | let text = t.render().expect("failed to render response");
124 |
125 | let call_tool_response = rmcp::model::CallToolResult {
126 | content: vec![rmcp::model::Content::text(&text)],
127 | structured_content: Some(json_value),
128 | is_error: Some(false),
129 | meta: None,
130 | };
131 |
132 | Ok(call_tool_response)
133 | }};
134 | }
135 |
136 | macro_rules! mcp_error {
137 | ($msg:expr_2021) => {
138 | Ok(rmcp::model::CallToolResult::error(vec![rmcp::model::Content::text($msg)]))
139 | };
140 | ($fmt:expr_2021, $($arg:tt)*) => {
141 | Ok(rmcp::model::CallToolResult::error(vec![rmcp::model::Content::text(format!($fmt, $($arg)*))]))
142 | };
143 | }
144 |
145 | pub(crate) use generate_capturable_methods;
146 | pub(crate) use generate_get_source_unit;
147 | pub(crate) use make_route;
148 | pub(crate) use mcp_error;
149 | pub(crate) use mcp_success;
150 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/context/mcp/callgraph/tool.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::{
2 | ast::{ASTNode, NodeID},
3 | context::{
4 | macros::{mcp_error, mcp_success},
5 | mcp::{
6 | MCPToolNamePool, ModelContextProtocolState, ModelContextProtocolTool,
7 | callgraph::{
8 | render::{
9 | CallgraphToolResponseBuilder, ContractDataBuilder,
10 | EntrypointFunctionDataBuilder,
11 | },
12 | utils::{build_post_order_nodes, build_raw_callgraph_for_entrypoint},
13 | },
14 | },
15 | },
16 | };
17 | use indoc::indoc;
18 | use rmcp::{
19 | ErrorData as McpError, handler::server::wrapper::Parameters, model::CallToolResult, schemars,
20 | };
21 | use serde::Deserialize;
22 | use std::sync::Arc;
23 |
24 | #[derive(Clone)]
25 | pub struct CallgraphTool {
26 | state: Arc<ModelContextProtocolState>,
27 | }
28 |
29 | #[derive(Deserialize, schemars::JsonSchema)]
30 | pub struct CallgraphPayload {
31 | /// The index of the compilation unit to analyze. Must be a positive integer starting from 1.
32 | /// Use the project overview tool first to see all available compilation units and their
33 | /// indices.
34 | pub compilation_unit_index: usize,
35 | /// The Node ID of the main contract to analyze. Obtain this from the list contracts tool,
36 | /// which returns Node IDs for all deployable contracts in the compilation unit. Each contract
37 | /// has a unique Node ID within its compilation unit.
38 | pub deployable_contract_node_id: NodeID,
39 | /// The Node ID of the specific entrypoint function in the contract to start exploring the
40 | /// callgraph from. Obtain this from contract surface area tool.
41 | pub entrypoint_function_node_id: NodeID,
42 | }
43 |
44 | impl ModelContextProtocolTool for CallgraphTool {
45 | type Input = CallgraphPayload;
46 |
47 | fn new(state: Arc<ModelContextProtocolState>) -> Self {
48 | Self { state }
49 | }
50 |
51 | fn name(&self) -> String {
52 | MCPToolNamePool::AderynExploreCallgraphFromEntrypoint.to_string()
53 | }
54 |
55 | fn description(&self) -> String {
56 | indoc! {
57 | "The callgraph provider tool maps and analyzes function execution flows within Solidity smart contracts \
58 | by tracing all possible internal function calls and modifier executions triggered by the given entrypoint\
59 | function's Node ID. It provides inheritance-aware analysis across contract hierarchies and \
60 | imported libraries. It returns compilation unit index and NodeIDs of the various functions in the call chain."
61 | }
62 | .to_string()
63 | }
64 |
65 | fn execute(&self, input: Parameters<Self::Input>) -> Result<CallToolResult, McpError> {
66 | let payload = input.0;
67 |
68 | if payload.compilation_unit_index < 1
69 | || payload.compilation_unit_index > self.state.contexts.len()
70 | {
71 | return mcp_error!(
72 | "Invalid compilation unit index: {}. Must be in range [1, {}]",
73 | payload.compilation_unit_index,
74 | self.state.contexts.len()
75 | );
76 | }
77 |
78 | let context = self
79 | .state
80 | .contexts
81 | .get(payload.compilation_unit_index - 1)
82 | .expect("Compilation unit index bounds check failed");
83 |
84 | let Some(ASTNode::ContractDefinition(contract)) =
85 | context.nodes.get(&payload.deployable_contract_node_id)
86 | else {
87 | return mcp_error!(
88 | "Node ID {} does not correspond to a contract definition",
89 | payload.deployable_contract_node_id
90 | );
91 | };
92 |
93 | let Some(ASTNode::FunctionDefinition(entrypoint)) =
94 | context.nodes.get(&payload.entrypoint_function_node_id)
95 | else {
96 | return mcp_error!(
97 | "Node ID {} does not correspond to a function",
98 | payload.entrypoint_function_node_id
99 | );
100 | };
101 |
102 | let contract_data = ContractDataBuilder::default()
103 | .name(contract.name.clone())
104 | .node_id(contract.id)
105 | .build()
106 | .expect("failed to build contract data");
107 |
108 | let entrypoint_function_data = EntrypointFunctionDataBuilder::default()
109 | .name(entrypoint.name.clone())
110 | .node_id(entrypoint.id)
111 | .build()
112 | .expect("failed to build entrypoint function data");
113 |
114 | let subgraph = build_raw_callgraph_for_entrypoint(context, contract, entrypoint)?;
115 | let post_order_data = build_post_order_nodes(context, &subgraph, entrypoint)?;
116 |
117 | let cg_response = CallgraphToolResponseBuilder::default()
118 | .compilation_unit_index(payload.compilation_unit_index)
119 | .contract(contract_data)
120 | .entrypoint_function(entrypoint_function_data)
121 | .graph(subgraph)
122 | .post_order_nodes(post_order_data)
123 | .build()
124 | .expect("failed to build callgraph tool response");
125 |
126 | mcp_success!(cg_response)
127 | }
128 | }
129 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/low/state_variable_read_external.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{
2 | collections::{BTreeMap, HashSet},
3 | error::Error,
4 | };
5 |
6 | use crate::ast::{
7 | ASTNode, ContractDefinition, Expression, Identifier, MemberAccess, NodeID, Visibility,
8 | };
9 |
10 | use crate::{
11 | capture,
12 | context::{
13 | browser::{ExtractFunctionCalls, ExtractVariableDeclarations},
14 | workspace::WorkspaceContext,
15 | },
16 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
17 | };
18 | use eyre::Result;
19 |
20 | #[derive(Default)]
21 | pub struct StateVariableReadExternalDetector {
22 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
23 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
24 | found_instances: BTreeMap<(String, usize, String), NodeID>,
25 | }
26 |
27 | impl IssueDetector for StateVariableReadExternalDetector {
28 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
29 | for contract in context.contract_definitions() {
30 | // Public state variables including the base contracts
31 | if let Ok(public_state_variable_names) =
32 | find_all_public_state_variables_names_for_contract(context, contract)
33 | {
34 | // Find all the `X`s that appear with the pattern `this.X()`
35 | let this_member_accesses =
36 | find_all_public_member_names_called_using_this_keyword_in_contract(
37 | context, contract,
38 | );
39 |
40 | for member_access in this_member_accesses {
41 | if public_state_variable_names.contains(&member_access.member_name) {
42 | capture!(self, context, member_access);
43 | }
44 | }
45 | }
46 | }
47 |
48 | Ok(!self.found_instances.is_empty())
49 | }
50 |
51 | fn severity(&self) -> IssueSeverity {
52 | IssueSeverity::Low
53 | }
54 |
55 | fn title(&self) -> String {
56 | String::from("State Variable is Read as External")
57 | }
58 |
59 | fn description(&self) -> String {
60 | String::from(
61 | "The contract reads it's own state variable using `this` which adds an unnecessary STATICCALL. Consider removing `this` to access the variable from storage.",
62 | )
63 | }
64 |
65 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
66 | self.found_instances.clone()
67 | }
68 |
69 | fn name(&self) -> String {
70 | IssueDetectorNamePool::StateVariableReadExternal.to_string()
71 | }
72 | }
73 |
74 | fn find_all_public_member_names_called_using_this_keyword_in_contract<'a>(
75 | context: &'a WorkspaceContext,
76 | contract: &ContractDefinition,
77 | ) -> Vec<&'a MemberAccess> {
78 | let mut member_names = vec![];
79 |
80 | let function_calls = ExtractFunctionCalls::from(contract).extracted;
81 | for function_call in function_calls {
82 | if let Expression::MemberAccess(MemberAccess { id, expression, .. }) =
83 | function_call.expression.as_ref()
84 | && let Expression::Identifier(Identifier { name, .. }) = expression.as_ref()
85 | && name == "this"
86 | && let Some(ASTNode::MemberAccess(member_access)) = context.nodes.get(id)
87 | {
88 | member_names.push(member_access)
89 | }
90 | }
91 |
92 | member_names
93 | }
94 |
95 | // Scans the linearized base contracts and returns a list of all the NodeIDs of public variable
96 | // declarations
97 | fn find_all_public_state_variables_names_for_contract(
98 | context: &WorkspaceContext,
99 | contract: &ContractDefinition,
100 | ) -> Result<HashSet<String>, Box<dyn Error>> {
101 | Ok(contract
102 | .linearized_base_contracts
103 | .iter()
104 | .flat_map(|ancestor_id| {
105 | if let Some(ancestor) = context.nodes.get(ancestor_id) {
106 | let public_variable_declarations =
107 | ExtractVariableDeclarations::from(ancestor).extracted;
108 | return Some(
109 | public_variable_declarations
110 | .into_iter()
111 | .filter(|declaration| {
112 | declaration.state_variable
113 | && declaration.visibility == Visibility::Public
114 | })
115 | .collect::<Vec<_>>(),
116 | );
117 | }
118 | None
119 | })
120 | .flatten()
121 | .map(|v| v.name.clone())
122 | .collect())
123 | }
124 |
125 | #[cfg(test)]
126 | mod public_variable_read_in_external_context_detector_tests {
127 |
128 | use crate::detect::{
129 | detector::IssueDetector,
130 | low::state_variable_read_external::StateVariableReadExternalDetector,
131 | };
132 |
133 | #[test]
134 |
135 | fn test_public_variable_read_in_external_context() {
136 | let context = crate::detect::test_utils::load_solidity_source_unit(
137 | "../tests/contract-playground/src/PublicVariableReadInExternalContext.sol",
138 | );
139 |
140 | let mut detector = StateVariableReadExternalDetector::default();
141 | let found = detector.detect(&context).unwrap();
142 | assert!(found);
143 | assert_eq!(detector.instances().len(), 4);
144 | }
145 | }
146 |
```
--------------------------------------------------------------------------------
/benchmarks/non-reentrant-before-others/report/relative_regression_small.svg:
--------------------------------------------------------------------------------
```
1 | <svg width="450" height="300" viewBox="0 0 450 300" xmlns="http://www.w3.org/2000/svg">
2 | <text x="15" y="130" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000" transform="rotate(270, 15, 130)">
3 | Total sample time (ms)
4 | </text>
5 | <text x="255" y="285" dy="-0.5ex" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
6 | Iterations (x 10^3)
7 | </text>
8 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="244" x2="75" y2="15"/>
9 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="139" y1="244" x2="139" y2="15"/>
10 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="203" y1="244" x2="203" y2="15"/>
11 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="267" y1="244" x2="267" y2="15"/>
12 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="331" y1="244" x2="331" y2="15"/>
13 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="395" y1="244" x2="395" y2="15"/>
14 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="244" x2="434" y2="244"/>
15 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="204" x2="434" y2="204"/>
16 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="164" x2="434" y2="164"/>
17 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="124" x2="434" y2="124"/>
18 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="84" x2="434" y2="84"/>
19 | <line opacity="0.2" stroke="#000000" stroke-width="1" x1="75" y1="44" x2="434" y2="44"/>
20 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="74,15 74,244 "/>
21 | <text x="65" y="244" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
22 | 0.0
23 | </text>
24 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,244 74,244 "/>
25 | <text x="65" y="204" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
26 | 20.0
27 | </text>
28 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,204 74,204 "/>
29 | <text x="65" y="164" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
30 | 40.0
31 | </text>
32 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,164 74,164 "/>
33 | <text x="65" y="124" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
34 | 60.0
35 | </text>
36 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,124 74,124 "/>
37 | <text x="65" y="84" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
38 | 80.0
39 | </text>
40 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,84 74,84 "/>
41 | <text x="65" y="44" dy="0.5ex" text-anchor="end" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
42 | 100.0
43 | </text>
44 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="69,44 74,44 "/>
45 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="75,245 434,245 "/>
46 | <text x="75" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
47 | 0
48 | </text>
49 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="75,245 75,250 "/>
50 | <text x="139" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
51 | 5
52 | </text>
53 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="139,245 139,250 "/>
54 | <text x="203" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
55 | 10
56 | </text>
57 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="203,245 203,250 "/>
58 | <text x="267" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
59 | 15
60 | </text>
61 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="267,245 267,250 "/>
62 | <text x="331" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
63 | 20
64 | </text>
65 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="331,245 331,250 "/>
66 | <text x="395" y="255" dy="0.76em" text-anchor="middle" font-family="sans-serif" font-size="9.67741935483871" opacity="1" fill="#000000">
67 | 25
68 | </text>
69 | <polyline fill="none" opacity="1" stroke="#000000" stroke-width="1" points="395,245 395,250 "/>
70 | <polyline fill="none" opacity="1" stroke="#E31A1C" stroke-width="1" points="75,244 434,15 "/>
71 | <polygon opacity="0.25" fill="#E31A1C" points="75,244 434,21 434,15 "/>
72 | <polyline fill="none" opacity="1" stroke="#1F78B4" stroke-width="1" points="75,244 434,63 "/>
73 | <polygon opacity="0.25" fill="#1F78B4" points="75,244 434,64 434,61 "/>
74 | </svg>
75 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/arbitrary_transfer_from.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{collections::BTreeMap, error::Error};
2 |
3 | use crate::{
4 | ast::{Expression, Identifier, NodeID},
5 | capture,
6 | context::{browser::ExtractFunctionCalls, workspace::WorkspaceContext},
7 | detect::{
8 | detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9 | helpers::{get_implemented_external_and_public_functions, has_msg_sender_binary_operation},
10 | },
11 | };
12 | use eyre::Result;
13 |
14 | #[derive(Default)]
15 | pub struct ArbitraryTransferFromDetector {
16 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
17 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
18 | found_instances: BTreeMap<(String, usize, String), NodeID>,
19 | }
20 |
21 | impl IssueDetector for ArbitraryTransferFromDetector {
22 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
23 | // Applying devtooligan's suggestion
24 | // * Operate on public and external functions only
25 | // * See that msg.sender is not checked
26 | // * Check that the argument passed in is from the parameter list of the said function
27 |
28 | let suspected_functions =
29 | get_implemented_external_and_public_functions(context).filter(|function_definition| {
30 | !has_msg_sender_binary_operation(&((*function_definition).into()))
31 | && function_definition.modifiers.is_empty() // If there are modifiers, assume
32 | // the function is safe because
33 | // sometime modifiers' definition
34 | // may not be in scope
35 | });
36 |
37 | for func in suspected_functions {
38 | let func_parameters_ids =
39 | &func.parameters.parameters.iter().map(|f| f.id).collect::<Vec<_>>();
40 |
41 | let transfer_func_calls = ExtractFunctionCalls::from(func)
42 | .extracted
43 | .into_iter()
44 | .filter(|function_call| {
45 | // For each function call, check if the function call is a member access
46 | // and if the member name is "transferFrom" or "safeTransferFrom", then check if
47 | // the first argument is valid If the first argument is
48 | // valid, add the function call to found_instances
49 | if let Expression::MemberAccess(member_access) = &*function_call.expression
50 | && (member_access.member_name == "transferFrom"
51 | || member_access.member_name == "safeTransferFrom")
52 | {
53 | return true;
54 | }
55 | false
56 | })
57 | .collect::<Vec<_>>();
58 |
59 | for func in transfer_func_calls {
60 | // Check if the first argument of the function call is valid
61 | // In function calls with 3 args, the first arg [0] is the `from` address
62 | // In function calls with 4 args, the second arg [1] is the `from` address
63 | let arg_index = if func.arguments.len() == 3 {
64 | 0
65 | } else if func.arguments.len() == 4 {
66 | 1
67 | } else {
68 | continue;
69 | };
70 |
71 | let arg = &func.arguments[arg_index];
72 |
73 | if let Expression::Identifier(Identifier {
74 | referenced_declaration: Some(referenced_id),
75 | ..
76 | }) = arg
77 | && func_parameters_ids.iter().any(|r| r == referenced_id)
78 | {
79 | capture!(self, context, func);
80 | }
81 | }
82 | }
83 |
84 | Ok(!self.found_instances.is_empty())
85 | }
86 |
87 | fn severity(&self) -> IssueSeverity {
88 | IssueSeverity::High
89 | }
90 |
91 | fn title(&self) -> String {
92 | String::from("Arbitrary `from` Passed to `transferFrom`")
93 | }
94 |
95 | fn description(&self) -> String {
96 | String::from(
97 | "Passing an arbitrary `from` address to `transferFrom` (or `safeTransferFrom`) can lead to loss of funds, because anyone can transfer tokens from the `from` address if an approval is made.",
98 | )
99 | }
100 |
101 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
102 | self.found_instances.clone()
103 | }
104 |
105 | fn name(&self) -> String {
106 | format!("{}", IssueDetectorNamePool::ArbitraryTransferFrom)
107 | }
108 | }
109 |
110 | #[cfg(test)]
111 | mod arbitrary_transfer_from_tests {
112 |
113 | use crate::detect::{
114 | detector::IssueDetector, high::arbitrary_transfer_from::ArbitraryTransferFromDetector,
115 | };
116 |
117 | #[test]
118 |
119 | fn test_arbitrary_transfer_from_detector_by_loading_contract_directly() {
120 | let context = crate::detect::test_utils::load_solidity_source_unit(
121 | "../tests/contract-playground/src/ArbitraryTransferFrom.sol",
122 | );
123 |
124 | let mut detector = ArbitraryTransferFromDetector::default();
125 | let found = detector.detect(&context).unwrap();
126 | assert!(found);
127 | assert_eq!(detector.instances().len(), 2);
128 | }
129 | }
130 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/detect/high/function_selector_collision.rs:
--------------------------------------------------------------------------------
```rust
1 | use std::{
2 | collections::{BTreeMap, HashMap},
3 | error::Error,
4 | };
5 |
6 | use crate::ast::NodeID;
7 |
8 | use crate::{
9 | capture,
10 | context::workspace::WorkspaceContext,
11 | detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
12 | };
13 | use eyre::Result;
14 |
15 | #[derive(Default)]
16 | pub struct FunctionSelectorCollisionDetector {
17 | // Keys are: [0] source file name, [1] line number, [2] character location of node.
18 | // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
19 | found_instances: BTreeMap<(String, usize, String), NodeID>,
20 | hints: BTreeMap<(String, usize, String), String>,
21 | }
22 |
23 | impl IssueDetector for FunctionSelectorCollisionDetector {
24 | fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
25 | // function_selector -> (function_name -> function_id)
26 | let mut selectors: HashMap<String, HashMap<String, Vec<NodeID>>> = HashMap::new();
27 |
28 | // PLAN
29 | // If we have > 1 function_name entries for any function_selector, then capture all the
30 | // corresponding NodeIDs
31 |
32 | for function in context.function_definitions() {
33 | if let Some(selector) = function.function_selector.as_ref() {
34 | let name = &function.name;
35 | match selectors.entry(selector.clone()) {
36 | std::collections::hash_map::Entry::Occupied(mut o) => {
37 | match o.get_mut().entry(name.clone()) {
38 | std::collections::hash_map::Entry::Occupied(mut o) => {
39 | o.get_mut().push(function.id);
40 | }
41 | std::collections::hash_map::Entry::Vacant(v) => {
42 | v.insert(vec![function.id]);
43 | }
44 | };
45 | }
46 | std::collections::hash_map::Entry::Vacant(v) => {
47 | let mut nested_entry = HashMap::new();
48 | nested_entry.insert(name.clone(), vec![function.id]);
49 | v.insert(nested_entry);
50 | }
51 | }
52 | }
53 | }
54 |
55 | for function_entries in selectors.values() {
56 | if function_entries.len() >= 2 {
57 | // Now we know that there is a collision + at least 2 different function names found
58 | // for that selector.
59 |
60 | // Capture the relevant functions
61 | for (function_name, function_ids) in function_entries {
62 | // Prepare data for the hint
63 | let all_colliding_function_names = function_entries
64 | .keys()
65 | .filter(|&x| x != function_name)
66 | .cloned()
67 | .collect::<Vec<_>>();
68 |
69 | for function_id in function_ids {
70 | // Prepare the hint
71 | let mut hint =
72 | String::from("collides with the following function name(s) in scope: ");
73 | hint.push_str(&all_colliding_function_names.join(", "));
74 |
75 | // Capture the function
76 | if let Some(node) = context.nodes.get(function_id) {
77 | capture!(self, context, node, hint);
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
84 | Ok(!self.found_instances.is_empty())
85 | }
86 |
87 | fn severity(&self) -> IssueSeverity {
88 | IssueSeverity::High
89 | }
90 |
91 | fn title(&self) -> String {
92 | String::from("Function Selector Collision")
93 | }
94 |
95 | fn description(&self) -> String {
96 | String::from(
97 | "Function selector collides with other functions. This may cause the solidity function dispatcher to invoke the wrong function if the functions happen to be included in the same contract through an inheritance hierarchy later down the line. It is recommended to rename this function or change its parameters.",
98 | )
99 | }
100 |
101 | fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
102 | self.found_instances.clone()
103 | }
104 |
105 | fn hints(&self) -> BTreeMap<(String, usize, String), String> {
106 | self.hints.clone()
107 | }
108 |
109 | fn name(&self) -> String {
110 | format!("{}", IssueDetectorNamePool::FunctionSelectorCollision)
111 | }
112 | }
113 |
114 | #[cfg(test)]
115 | mod function_signature_collision {
116 |
117 | use crate::detect::{
118 | detector::IssueDetector,
119 | high::function_selector_collision::FunctionSelectorCollisionDetector,
120 | };
121 |
122 | #[test]
123 |
124 | fn test_function_signature_collision() {
125 | let context = crate::detect::test_utils::load_solidity_source_unit(
126 | "../tests/contract-playground/src/FunctionSignatureCollision.sol",
127 | );
128 |
129 | let mut detector = FunctionSelectorCollisionDetector::default();
130 | let found = detector.detect(&context).unwrap();
131 | assert!(found);
132 | assert_eq!(detector.instances().len(), 2);
133 | }
134 | }
135 |
```
--------------------------------------------------------------------------------
/aderyn_driver/src/compile.rs:
--------------------------------------------------------------------------------
```rust
1 | #![allow(unstable_name_collisions)]
2 |
3 | use aderyn_core::{
4 | ast::SourceUnit, context::workspace::WorkspaceContext, visitor::ast_visitor::Node,
5 | };
6 | use rayon::iter::{IntoParallelIterator, ParallelIterator};
7 | use solidity_ast::{
8 | AstSourceFile, ExcludeConfig, IncludeConfig, ProjectConfigInput, ProjectConfigInputBuilder,
9 | Source, SourcesConfig, derive_ast_and_evm_info,
10 | };
11 | use std::{path::PathBuf, str::FromStr};
12 |
13 | use crate::{
14 | display::{display_configuration_info, display_header, display_ingesting_message},
15 | process::PreprocessedConfig,
16 | };
17 |
18 | pub fn compile_project(
19 | preprocessed_config: PreprocessedConfig,
20 | lsp_mode: bool,
21 | verbose: bool,
22 | ) -> Result<(Vec<WorkspaceContext>, ProjectConfigInput), Box<dyn std::error::Error + Sync + Send>> {
23 | // Decompose pre-processed config
24 | let PreprocessedConfig { root_path, src, include, exclude } = preprocessed_config;
25 |
26 | // Process the pre-processed config using Cyfrin/solidity-ast-rs to translate to runtime values
27 | let path_form_src = |src: &str| -> PathBuf { PathBuf::from_str(src).unwrap() };
28 | let processed_config = ProjectConfigInputBuilder::new(&root_path)
29 | .with_sources(src.map_or_default(|src| SourcesConfig::Specific(path_form_src(&src))))
30 | .with_exclude(exclude.map_or_default(|exclude| ExcludeConfig::Specific(exclude.to_vec())))
31 | .with_include(include.map_or_default(|include| IncludeConfig::Specific(include.to_vec())))
32 | .build()?;
33 |
34 | if verbose {
35 | display_configuration_info(&processed_config);
36 | display_header(&processed_config, "Compiling Abstract Syntax Trees");
37 | }
38 |
39 | // Derive the raw AST content from the source files as per the processed config
40 | let derived_ast_evm_info = match derive_ast_and_evm_info(&processed_config) {
41 | Ok(results) => results,
42 | Err(e) => {
43 | eprintln!("Failed to Derive AST & EVM Info: {}", e);
44 | return Err("Failed to Derive AST / EVM info".into());
45 | }
46 | };
47 |
48 | // Parse the AST content into WorkspaceContexts
49 | let contexts_results = derived_ast_evm_info
50 | .versioned_asts
51 | .into_par_iter() // TODO: Bench to see which is faster - iter() or par_iter()?
52 | .map(|ast_info| {
53 | let mut context = WorkspaceContext::default();
54 |
55 | let sources = ast_info.sources.0;
56 | let sources_ast = ast_info.compiler_output.sources;
57 | let included = ast_info.included_files;
58 |
59 | for cerror in ast_info.compiler_output.errors {
60 | if cerror.severity.is_error() {
61 | eprintln!("Compilation Error: {}", cerror);
62 | return None;
63 | }
64 | }
65 |
66 | if verbose {
67 | display_ingesting_message(&sources_ast, &included, &ast_info.version.to_string());
68 | }
69 | for (source_path, ast_source_file) in sources_ast {
70 | let content = sources.get(&source_path).expect("content not found");
71 | absorb_ast_content_into_context(ast_source_file, &mut context, content.clone());
72 | context.src_filepaths.push(source_path.display().to_string());
73 | }
74 |
75 | context.evm_version = derived_ast_evm_info.evm_version;
76 | context.included = included;
77 |
78 | Some(context)
79 | })
80 | .collect::<Vec<_>>();
81 |
82 | // Only when not in LSP mode, error out if some context had compilation errors
83 | if !lsp_mode && contexts_results.iter().any(|c| c.is_none()) {
84 | std::process::exit(1);
85 | }
86 |
87 | if verbose {
88 | display_header(&processed_config, "Scanning Contracts");
89 | }
90 |
91 | // Return the parsed ASTs as a vector of Workspace Contexts
92 | Ok((contexts_results.into_iter().flatten().collect(), processed_config))
93 | }
94 |
95 | fn absorb_ast_content_into_context(
96 | ast_source_file: AstSourceFile,
97 | context: &mut WorkspaceContext,
98 | content: Source,
99 | ) {
100 | let Some(ast_content) = ast_source_file.ast else {
101 | eprintln!("Warning: AST not found in output");
102 | return;
103 | };
104 |
105 | let Ok(mut source_unit) = serde_json::from_str::<SourceUnit>(&ast_content) else {
106 | eprintln!("Unable to serialize Source Unit from AST - \n{}\n", &ast_content);
107 | let error = serde_json::from_str::<SourceUnit>(&ast_content).unwrap_err();
108 | eprintln!("{:?}", error);
109 | std::process::exit(1);
110 | };
111 |
112 | // Set the source
113 | source_unit.source = Some(content.content.to_string());
114 |
115 | // Read the relative filepath
116 | let filepath = source_unit.absolute_path.as_ref().unwrap();
117 |
118 | // Reset absolute path.
119 | source_unit.absolute_path = Some(filepath.to_string());
120 |
121 | // TODO: Change absolute_path to type Path instead of String so we don't lose any unicode
122 | // characters (in the minority of cases)
123 |
124 | source_unit.accept(context).unwrap_or_else(|err| {
125 | // Exit with a non-zero exit code
126 | eprintln!("Error loading AST into WorkspaceContext");
127 | eprintln!("{:?}", err);
128 | std::process::exit(1);
129 | });
130 | }
131 |
```
--------------------------------------------------------------------------------
/tests/ast/userDefinedValueType.json:
--------------------------------------------------------------------------------
```json
1 | {"absolutePath":"a","exportedSymbols":{"C":[27],"MyAddress":[2],"MyUInt":[4],"f":[16]},"id":28,"nodeType":"SourceUnit","nodes":[{"canonicalName":"MyAddress","id":2,"name":"MyAddress","nameLocation":"5:9:1","nodeType":"UserDefinedValueTypeDefinition","src":"0:26:1","underlyingType":{"id":1,"name":"address","nodeType":"ElementaryTypeName","src":"18:7:1","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}},{"canonicalName":"MyUInt","id":4,"name":"MyUInt","nameLocation":"32:6:1","nodeType":"UserDefinedValueTypeDefinition","src":"27:20:1","underlyingType":{"id":3,"name":"uint","nodeType":"ElementaryTypeName","src":"42:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}},{"body":{"id":15,"nodeType":"Block","src":"61:34:1","statements":[{"assignments":[9],"declarations":[{"constant":false,"id":9,"mutability":"mutable","name":"a","nameLocation":"77:1:1","nodeType":"VariableDeclaration","scope":15,"src":"67:11:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_userDefinedValueType$_MyAddress_$2","typeString":"MyAddress"},"typeName":{"id":8,"nodeType":"UserDefinedTypeName","pathNode":{"id":7,"name":"MyAddress","nameLocations":["67:9:1"],"nodeType":"IdentifierPath","referencedDeclaration":2,"src":"67:9:1"},"referencedDeclaration":2,"src":"67:9:1","typeDescriptions":{"typeIdentifier":"t_userDefinedValueType$_MyAddress_$2","typeString":"MyAddress"}},"visibility":"internal"}],"id":10,"nodeType":"VariableDeclarationStatement","src":"67:11:1"},{"assignments":[13],"declarations":[{"constant":false,"id":13,"mutability":"mutable","name":"b","nameLocation":"91:1:1","nodeType":"VariableDeclaration","scope":15,"src":"84:8:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_userDefinedValueType$_MyUInt_$4","typeString":"MyUInt"},"typeName":{"id":12,"nodeType":"UserDefinedTypeName","pathNode":{"id":11,"name":"MyUInt","nameLocations":["84:6:1"],"nodeType":"IdentifierPath","referencedDeclaration":4,"src":"84:6:1"},"referencedDeclaration":4,"src":"84:6:1","typeDescriptions":{"typeIdentifier":"t_userDefinedValueType$_MyUInt_$4","typeString":"MyUInt"}},"visibility":"internal"}],"id":14,"nodeType":"VariableDeclarationStatement","src":"84:8:1"}]},"id":16,"implemented":true,"kind":"freeFunction","modifiers":[],"name":"f","nameLocation":"57:1:1","nodeType":"FunctionDefinition","parameters":{"id":5,"nodeType":"ParameterList","parameters":[],"src":"58:2:1"},"returnParameters":{"id":6,"nodeType":"ParameterList","parameters":[],"src":"61:0:1"},"scope":28,"src":"48:47:1","stateMutability":"nonpayable","virtual":false,"visibility":"internal"},{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":27,"linearizedBaseContracts":[27],"name":"C","nameLocation":"105:1:1","nodeType":"ContractDefinition","nodes":[{"canonicalName":"C.MyAddress","id":18,"name":"MyAddress","nameLocation":"118:9:1","nodeType":"UserDefinedValueTypeDefinition","src":"113:26:1","underlyingType":{"id":17,"name":"address","nodeType":"ElementaryTypeName","src":"131:7:1","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}},{"canonicalName":"C.MyUInt","id":20,"name":"MyUInt","nameLocation":"149:6:1","nodeType":"UserDefinedValueTypeDefinition","src":"144:20:1","underlyingType":{"id":19,"name":"uint","nodeType":"ElementaryTypeName","src":"159:4:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}},{"constant":false,"functionSelector":"97682884","id":26,"mutability":"mutable","name":"m","nameLocation":"205:1:1","nodeType":"VariableDeclaration","scope":27,"src":"169:37:1","stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_mapping$_t_userDefinedValueType$_MyAddress_$18_$_t_userDefinedValueType$_MyUInt_$20_$","typeString":"mapping(C.MyAddress => C.MyUInt)"},"typeName":{"id":25,"keyType":{"id":22,"nodeType":"UserDefinedTypeName","pathNode":{"id":21,"name":"MyAddress","nameLocations":["177:9:1"],"nodeType":"IdentifierPath","referencedDeclaration":18,"src":"177:9:1"},"referencedDeclaration":18,"src":"177:9:1","typeDescriptions":{"typeIdentifier":"t_userDefinedValueType$_MyAddress_$18","typeString":"C.MyAddress"}},"nodeType":"Mapping","src":"169:28:1","typeDescriptions":{"typeIdentifier":"t_mapping$_t_userDefinedValueType$_MyAddress_$18_$_t_userDefinedValueType$_MyUInt_$20_$","typeString":"mapping(C.MyAddress => C.MyUInt)"},"valueType":{"id":24,"nodeType":"UserDefinedTypeName","pathNode":{"id":23,"name":"MyUInt","nameLocations":["190:6:1"],"nodeType":"IdentifierPath","referencedDeclaration":20,"src":"190:6:1"},"referencedDeclaration":20,"src":"190:6:1","typeDescriptions":{"typeIdentifier":"t_userDefinedValueType$_MyUInt_$20","typeString":"C.MyUInt"}}},"visibility":"public"}],"scope":28,"src":"96:113:1","usedErrors":[]}],"src":"0:210:1"}
2 |
```
--------------------------------------------------------------------------------
/aderyn_core/src/ast/impls/disp/statements.rs:
--------------------------------------------------------------------------------
```rust
1 | use crate::ast::*;
2 | use std::fmt::Display;
3 |
4 | impl Display for Statement {
5 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6 | match self {
7 | Statement::VariableDeclarationStatement(stmt) => stmt.fmt(f),
8 | Statement::IfStatement(stmt) => stmt.fmt(f),
9 | Statement::ForStatement(stmt) => stmt.fmt(f),
10 | Statement::WhileStatement(stmt) => stmt.fmt(f),
11 | Statement::EmitStatement(stmt) => stmt.fmt(f),
12 | Statement::TryStatement(stmt) => stmt.fmt(f),
13 | Statement::RevertStatement(stmt) => stmt.fmt(f),
14 | Statement::UncheckedBlock(stmt) => stmt.fmt(f),
15 | Statement::Return(stmt) => stmt.fmt(f),
16 | Statement::ExpressionStatement(stmt) => stmt.fmt(f),
17 | Statement::InlineAssembly(..) => {
18 | f.write_str("assembly { /* WARNING: not implemented */ }")
19 | }
20 | _ => f.write_str("unrecognized!"),
21 | }
22 | }
23 | }
24 |
25 | impl Display for ExpressionStatement {
26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 | f.write_fmt(format_args!("{}", self.expression))
28 | }
29 | }
30 |
31 | impl Display for VariableDeclarationStatement {
32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 | if self.declarations.len() == 1 {
34 | if let Some(declaration) = self.declarations[0].as_ref() {
35 | f.write_fmt(format_args!("{declaration}"))?;
36 | } else {
37 | f.write_str("()")?;
38 | }
39 | } else {
40 | f.write_str("(")?;
41 |
42 | for (i, declaration) in self.declarations.iter().enumerate() {
43 | if i > 0 {
44 | f.write_str(", ")?;
45 | }
46 |
47 | if let Some(declaration) = declaration {
48 | f.write_fmt(format_args!("{declaration}"))?;
49 | }
50 | }
51 |
52 | f.write_str(")")?;
53 | }
54 |
55 | if let Some(initial_value) = self.initial_value.as_ref() {
56 | f.write_fmt(format_args!(" = {initial_value}"))?;
57 | }
58 |
59 | Ok(())
60 | }
61 | }
62 |
63 | impl Display for BlockOrStatement {
64 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 | match self {
66 | BlockOrStatement::Block(block) => block.fmt(f),
67 | BlockOrStatement::Statement(statement) => statement.fmt(f),
68 | }
69 | }
70 | }
71 |
72 | impl Display for IfStatement {
73 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 | f.write_fmt(format_args!("if ({}) {}", self.condition, self.true_body))?;
75 |
76 | if let Some(false_body) = self.false_body.as_ref() {
77 | f.write_fmt(format_args!("\nelse {false_body}"))?;
78 | }
79 |
80 | Ok(())
81 | }
82 | }
83 |
84 | impl Display for ForStatement {
85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 | f.write_str("for (")?;
87 |
88 | if let Some(initialization_expression) = self.initialization_expression.as_ref() {
89 | f.write_fmt(format_args!("{:?}", initialization_expression))?;
90 | }
91 |
92 | f.write_str("; ")?;
93 |
94 | if let Some(condition) = self.condition.as_ref() {
95 | f.write_fmt(format_args!("{condition}"))?;
96 | }
97 |
98 | f.write_str("; ")?;
99 |
100 | if let Some(loop_expression) = self.loop_expression.as_ref() {
101 | f.write_fmt(format_args!("{loop_expression}"))?;
102 | }
103 |
104 | f.write_fmt(format_args!(") {}", self.body))
105 | }
106 | }
107 |
108 | impl Display for WhileStatement {
109 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 | f.write_fmt(format_args!("while ({}) {}", self.condition, self.body))
111 | }
112 | }
113 |
114 | impl Display for DoWhileStatement {
115 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 | f.write_fmt(format_args!("do {} while({});", self.body, self.condition))
117 | }
118 | }
119 |
120 | impl Display for EmitStatement {
121 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 | f.write_fmt(format_args!("emit {}", self.event_call))
123 | }
124 | }
125 |
126 | impl Display for TryStatement {
127 | fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 | unimplemented!()
129 | }
130 | }
131 |
132 | impl Display for RevertStatement {
133 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 | f.write_fmt(format_args!("revert {}", self.error_call))
135 | }
136 | }
137 |
138 | impl Display for TryCatchClause {
139 | fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 | unimplemented!()
141 | }
142 | }
143 |
144 | impl Display for Return {
145 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 | f.write_str("return")?;
147 |
148 | if let Some(expression) = self.expression.as_ref() {
149 | f.write_fmt(format_args!(" {expression}"))?;
150 | }
151 |
152 | Ok(())
153 | }
154 | }
155 |
156 | impl Display for Break {
157 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 | f.write_str("break;")
159 | }
160 | }
161 |
162 | impl Display for Continue {
163 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 | f.write_str("continue;")
165 | }
166 | }
167 |
```