How to Write a Detector in Aderyn Step by Step
SolidityAderynStatic AnalysisSmart Contract SecurityRust

How to Write a Detector in Aderyn Step by Step

15 de mayo de 2025Josephine
Creating the division_before_multiplication Detector: Our Process Explained
In Solidity, a common but costly error is dividing before multiplying. Because Solidity uses integer division by default, any decimal result gets silently truncated, often down to zero. This can introduce serious precision errors, especially in financial calculations, staking logic, and other math-heavy contracts. And the worst part? The compiler won’t throw any warnings.
That is exactly the kind of issue static analysis tools like Aderyn are built to catch.
In this guide, you’ll learn how to build a custom detector in Aderyn, a Rust-based static analyzer for Solidity. We’ll walk through everything step by step, from writing a vulnerable contract and inspecting its AST, to implementing and testing the division_before_multiplication detector in Rust, so you can follow along or use it as a base for writing your own.
The detector’s job is simple: flag any case where division happens before multiplication in a single expression, so it never slips past code review again.
Let’s get started.

What is Aderyn?

Aderyn is an open-source Rust-based static analyzer for Solidity smart contracts. It helps protocol engineers and security researchers catch bugs before they reach production. Aderyn highlights potential issues by inspecting the contract’s Abstract Syntax Tree (AST) and makes it easy to build your own custom detectors. It runs quickly from the command line and is simple to integrate into your development or auditing workflow.
Now that you know what Aderyn is, let’s get to work and build your first custom detector.

Step-by-Step Guide to Developing the division_before_multiplication Detector

Step One: Knowing Which Vulnerability to Detect

In this section, we need to identify the vulnerability we want to detect and understand how to identify it in our AST (Abstract Syntax Tree).
An AST is a structured representation of code that compilers primarily use to read code and generate target binaries. It is often stored as JSON.
In this case, we are going to automate the detection of a mathematical vulnerability in Solidity, which involves performing divisions before multiplications. This is a significant error due to the loss of precision in mathematical operations caused by the compiler's handling of decimals in the results.

Step Two: Write Our Own Contract Containing the Vulnerability

In this step, we will create a smart contract that includes the specific vulnerability we want to detect. This will allow us to test and refine our detector. Here is an example of a Solidity contract with the division-before-multiplication issue:
1pragma solidity 0.8.20;
2
3contract DivisionBeforeMultiplication {
4 uint public result;
5
6 function calculateWrong(uint a, uint b, uint c, uint d) external {
7 result = a * d + b / c * b / d;
8 }
9
10 function calculateAlsoWrong(uint a, uint b, uint c) external {
11 result = (a + b / c * b) * c;
12 }
13
14 function calculateAl(uint a, uint b, uint c) external {
15 result = (a / b * c);
16 }
17
18 function calculateStillWrong(uint a, uint b, uint c) external {
19 result = a + b / c * b * c;
20 }
21
22 function calculateCorrect(uint a, uint b, uint c) external {
23 result = a + b * b / c + b * c;
24 }
25
26 function calculateAlsoCorrect(uint a, uint b, uint c, uint d) external {
27 result = (a + ((b * d) / (c * b))) * d;
28 }
29}

Step Three: Understand the .json AST File Result

To begin understanding how we can extract information about the problem and write our detector to identify it, we need to analyze the .json AST file.
The AST (Abstract Syntax Tree) provides a structured representation of the Solidity code, which can be used to pinpoint where the vulnerability occurs. By examining the AST, we can determine how divisions and multiplications are represented and identify patterns that indicate the division-before-multiplication issue.
Generate the .json AST File: Use the Solidity compiler to generate the AST for your contract.
Examine the AST Structure: Open the generated DivisionBeforeMultiplication.json file and look for the nodes representing division and multiplication operations. These nodes will contain information about the operation type and the order of execution.
Identify Relevant Nodes: Look for BinaryOperation nodes in the AST. Each BinaryOperation node will have details about the operation (/ for division and * for multiplication) and the operands involved.
Determine the Pattern: Identify the pattern where a division operation is followed by a multiplication operation without proper handling to ensure precision. This pattern will be the basis for your detector.

Step Four: Writing the Detector

Let's break down the implementation of the DivisionBeforeMultiplication Detector step by step.
  1. Define the Detector Structure We define a structure to hold instances of the detected issue:
1pub struct DivisionBeforeMultiplicationDetector {
2 found_instances: BTreeMap<(String, usize, String), NodeID>,
3}
This structure uses a BTreeMap to store instances of the vulnerability. The keys are tuples consisting of the source file name, line number, and a description of the issue. NodeID represents the node in the AST where the issue was found.
  1. Implement theIssueDetector Trait We implement the theIssueDetector trait for our detector. This trait requires a detect method that performs the analysis.
1 fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
2 // Iterate over all binary operations in the context and filter for multiplication operations (*)
3 for op in context.binary_operations().iter().filter(|op| op.operator == "*") {
4 // Check if the left operand of the multiplication is a binary operation (i.e., another operation)
5 if let Expression::BinaryOperation(left_op) = op.left_expression.as_ref() {
6 // Check if this left operation is a division
7 if left_op.operator == "/" {
8 // Capture the instance of the vulnerability
9 capture!(self, context, left_op)
10 }
11 }
12 }
13 // Return true if any instances were found, otherwise false
14 Ok(!self.found_instances.is_empty())
15 }
16}
  1. Detailed Logic
  • Iterate Over Binary Operations: We use context.binary_operations() to get all binary operations and filter to select only multiplication operations.
  • Check the Left Operand: This checks if the left operand of the multiplication is another binary operation.
  • Identify Division Operation: We then check if this left binary operation is a division.
Capture the Instance: If both conditions are met, we capture this instance as a potential vulnerability.

Step Five: Add your Detector to the mod.rs file

image

Step Six: Register your detector

image

Step Seven: Run your custom detector locally

Code complete:

1use std::error::Error;
2
3use crate::ast::NodeID;
4
5use crate::capture;
6use crate::detect::detector::IssueDetectorNamePool;
7use crate::{
8 ast::Expression,
9 context::workspace_context::WorkspaceContext,
10 detect::detector::{IssueDetector, IssueSeverity},
11};
12use eyre::Result;
13
14#[derive(Default)]
15pub struct DivisionBeforeMultiplicationDetector {
16 // Keys are source file name, line number, and description
17 found_instances: BTreeMap<(String, usize, String), NodeID>,
18}
19
20impl IssueDetector for DivisionBeforeMultiplicationDetector {
21 fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
22 for op in context
23 .binary_operations()
24 .iter()
25 .filter(|op| op.operator == "*")
26 {
27 if let Expression::BinaryOperation(left_op) = op.left_expression.as_ref() {
28 if left_op.operator == "/" {
29 capture!(self, context, left_op)
30 }
31 }
32 }
33
34 Ok(!self.found_instances.is_empty())
35 }
36
37 fn severity(&self) -> IssueSeverity {
38 IssueSeverity::Low
39 }
40
41 fn title(&self) -> String {
42 String::from("Incorrect Order of Division and Multiplication")
43 }
44
45 fn description(&self) -> String {
46 String::from("Division operations followed directly by multiplication operations can lead to precision loss due to the way integer arithmetic is handled in Solidity.")
47 }
48
49 fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
50 self.found_instances.clone()
51 }
52
53 fn name(&self) -> String {
54 format!("{}", IssueDetectorNamePool::DivisionBeforeMultiplication)
55 }
56}
57
58#[cfg(test)]
59mod division_before_multiplication_detector_tests {
60 use super::DivisionBeforeMultiplicationDetector;
61 use crate::detect::detector::{detector_test_helpers::load_contract, IssueDetector};
62
63 #[test]
64 fn test_template_detector() {
65 let context = load_contract(
66 "../tests/contract-playground/out/DivisionBeforeMultiplication.sol/DivisionBeforeMultiplication.json",
67 );
68
69 let mut detector = DivisionBeforeMultiplicationDetector::default();
70 let found = detector.detect(&context).unwrap();
71 assert!(found);
72 assert_eq!(detector.instances().len(), 4);
73 assert_eq!(
74 detector.severity(),
75 crate::detect::detector::IssueSeverity::Low
76 );
77 assert_eq!(
78 detector.title(),
79 String::from("Incorrect Order of Division and Multiplication")
80 );
81 assert_eq!(
82 detector.description(),
83 String::from("Division operations followed directly by multiplication operations can lead to precision loss due to the way integer arithmetic is handled in Solidity.")
84 );
85 }
86}

Resources:

oog
zealynx

Subscribe to Our Newsletter

Stay updated with our latest security insights and blog posts

© 2024 Zealynx