Cyfrin

Useful Detector Patterns

Common AST traversal techniques

Useful Detector Patterns

Learn common patterns for navigating the AST in custom detectors.

Traversing Down

Extractor Pattern

Use extractors to find specific node types:

use crate::extractors::*;

// Find all function definitions
for function in context.get_functions() {
    // Process function
}

// Find all state variables
for variable in context.get_state_variables() {
    // Process variable
}

.children() Method

Navigate to child nodes:

for child in node.children() {
    // Process each child node
}

Traversing Up

.parent() Method

Access parent node:

if let Some(parent) = node.parent() {
    // Check parent node
}

.closest_ancestor_of_type()

Find nearest ancestor of specific type:

if let Some(contract) = function.closest_ancestor_of_type(NodeType::Contract) {
    // Function is inside this contract
}

.ancestral_line()

Get all ancestors up to root:

for ancestor in node.ancestral_line() {
    // Process each ancestor
}

Common Patterns

Find Functions with Modifier

for function in context.get_functions() {
    if function.has_modifier("nonReentrant") {
        // Function uses nonReentrant
    }
}

Find State Variables of Type

for variable in context.get_state_variables() {
    if variable.type_name == "address" {
        // Address state variable
    }
}

Find External Calls

for function in context.get_functions() {
    for statement in function.body.statements() {
        if let Statement::ExternalCall(call) = statement {
            // Found external call
        }
    }
}

Check Function Visibility

for function in context.get_functions() {
    if function.visibility == Visibility::Public {
        // Public function
    }
}

Example: Find Unprotected Functions

impl Detector for UnprotectedFunctionDetector {
    fn detect(&self, context: &WorkspaceContext) -> Vec<Finding> {
        context.get_functions()
            .filter(|f| f.visibility == Visibility::Public)
            .filter(|f| !f.has_modifier("onlyOwner"))
            .filter(|f| !f.has_modifier("nonReentrant"))
            .map(|f| self.capture(f))
            .collect()
    }
}

Advanced Patterns

Recursive Tree Walk

fn walk_node(node: &Node, findings: &mut Vec<Finding>) {
    // Check current node
    if is_vulnerable(node) {
        findings.push(create_finding(node));
    }
    
    // Recursively check children
    for child in node.children() {
        walk_node(child, findings);
    }
}

Context-Aware Detection

// Check if function is in a library
if let Some(parent_contract) = function.closest_ancestor_of_type(NodeType::Contract) {
    if parent_contract.kind == ContractKind::Library {
        // Different rules for library functions
    }
}

Next Steps