How to Write a Detector in Aderyn Step by Step
Table of contents
- What will you find here?
- Introduction
- Step-by-Step Guide to Developing thedivision_before_multiplication Detector
- First Step: Knowing Which Vulnerability to Detect
- Step Two: Write Our Own Contract Containing the Vulnerability
- Step Three: Understand the .json AST File Result
- Step Four: Writing the Detector
- Step five: Add your Detector to the mod.rs file
- Step six: Register your detector
- Step seven: Run your custom detector locally
- Code complete:
- Resources:
In this post, you'll learn how to develop a custom detector in Aderyn, a Rust-based static analyzer for Solidity smart contracts. We'll guide you through creating the `division_before_multiplication` detector, from understanding the vulnerability and writing a test contract to analyzing the AST and implementing the detector in Rust. By the end, you'll be equipped to identify and capture instances where division operations precede multiplication, potentially causing precision loss in Solidity code.
What will you find here?
In this post, you will learn from scratch how to write a detector in Aderyn. Specifically, we will explain how to create the division_before_multiplication
detector step by step, so you can try writing your own based on this information.
Introduction
What is Aderyn?
Aderyn is an open-source Rust-based static analyzer for Solidity smart contracts. It helps protocol engineers and security researchers identify vulnerabilities in Solidity codebases, highlighting potential issues and integrating seamlessly into development workflows with fast command-line functionality and custom detector frameworks.
Step-by-Step Guide to Developing thedivision_before_multiplication
Detector
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:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
contract DivisionBeforeMultiplication {
uint public result;
function calculateWrong(uint a, uint b, uint c, uint d) external {
result = a * d + b / c * b / d;
}
function calculateAlsoWrong(uint a, uint b, uint c) external {
result = (a + b / c * b) * c;
}
function calculateAl(uint a, uint b, uint c) external {
result = (a / b * c);
}
function calculateStillWrong(uint a, uint b, uint c) external {
result = a + b / c * b * c;
}
function calculateCorrect(uint a, uint b, uint c) external {
result = a + b * b / c + b * c;
}
function calculateAlsoCorrect(uint a, uint b, uint c, uint d) external {
result = (a + ((b * d) / (c * b))) * d;
}
}
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. EachBinaryOperation
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 DivisionBeforeMultiplicationDetector
step by step.
- Define the Detector Structure
We define a structure to hold instances of the detected issue:
pub struct DivisionBeforeMultiplicationDetector {
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
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.
- Implement the
IssueDetector
Trait
We implement the IssueDetector
trait for our detector. This trait requires a detect
method that performs the analysis.
impl IssueDetector for DivisionBeforeMultiplicationDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// Iterate over all binary operations in the context and filter for multiplication operations (*)
for op in context.binary_operations().iter().filter(|op| op.operator == "*") {
// Check if the left operand of the multiplication is a binary operation (i.e., another operation)
if let Expression::BinaryOperation(left_op) = op.left_expression.as_ref() {
// Check if this left operation is a division
if left_op.operator == "/" {
// Capture the instance of the vulnerability
capture!(self, context, left_op)
}
}
}
// Return true if any instances were found, otherwise false
Ok(!self.found_instances.is_empty())
}
}
- Detailed Logic
Iterate Over Binary Operations: We use
context.binary_operations()
to get all binary operations andfilter
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
Step six: Register your detector
Step seven: Run your custom detector locally
cargo run -- ./tests/contract-playground
Code complete:
use std::collections::BTreeMap;
use std::error::Error;
use crate::ast::NodeID;
use crate::capture;
use crate::detect::detector::IssueDetectorNamePool;
use crate::{
ast::Expression,
context::workspace_context::WorkspaceContext,
detect::detector::{IssueDetector, IssueSeverity},
};
use eyre::Result;
#[derive(Default)]
pub struct DivisionBeforeMultiplicationDetector {
// Keys are source file name, line number, and description
found_instances: BTreeMap<(String, usize, String), NodeID>,
}
impl IssueDetector for DivisionBeforeMultiplicationDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for op in context
.binary_operations()
.iter()
.filter(|op| op.operator == "*")
{
if let Expression::BinaryOperation(left_op) = op.left_expression.as_ref() {
if left_op.operator == "/" {
capture!(self, context, left_op)
}
}
}
Ok(!self.found_instances.is_empty())
}
fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}
fn title(&self) -> String {
String::from("Incorrect Order of Division and Multiplication")
}
fn description(&self) -> String {
String::from("Division operations followed directly by multiplication operations can lead to precision loss due to the way integer arithmetic is handled in Solidity.")
}
fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}
fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::DivisionBeforeMultiplication)
}
}
#[cfg(test)]
mod division_before_multiplication_detector_tests {
use super::DivisionBeforeMultiplicationDetector;
use crate::detect::detector::{detector_test_helpers::load_contract, IssueDetector};
#[test]
fn test_template_detector() {
let context = load_contract(
"../tests/contract-playground/out/DivisionBeforeMultiplication.sol/DivisionBeforeMultiplication.json",
);
let mut detector = DivisionBeforeMultiplicationDetector::default();
let found = detector.detect(&context).unwrap();
assert!(found);
assert_eq!(detector.instances().len(), 4);
assert_eq!(
detector.severity(),
crate::detect::detector::IssueSeverity::Low
);
assert_eq!(
detector.title(),
String::from("Incorrect Order of Division and Multiplication")
);
assert_eq!(
detector.description(),
String::from("Division operations followed directly by multiplication operations can lead to precision loss due to the way integer arithmetic is handled in Solidity.")
);
}
}
Resources:
Subscribe to my newsletter
Read articles from Zealynx Security directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Zealynx Security
Zealynx Security
At Zealynx wea re providing Smart Contract Security Reviews with the highly efficient security testing tools used by the top companies in Web3. An Audit with Zealynx keeps your current code safe now and after any changes you implement later on. That's accomplished by providing with each audit a test suite of Fuzz tests and Formal Verification.