debtmap 0.21.1

Code complexity and technical debt analyzer
Documentation
use std::path::Path;

use crate::analyzers::solidity::dependencies::extract_dependencies;
use crate::analyzers::solidity::parser::{node_line, node_text, parse_source};
use crate::core::{Dependency, DependencyKind};

use super::extract_solidity;

const KITCHEN_SINK: &str = r#"pragma solidity ^0.8.20;
import "./Base.sol";

contract Base {
    uint256 internal value;

    modifier onlyReady() {
        _;
    }

    function read(uint256 amount) public view returns (uint256 doubled) {
        return value + amount;
    }
}

contract Vault is Base {
    mapping(address => uint256) private balances;

    constructor() {
        value = 1;
    }

    receive() external payable {}

    function deposit(uint256 amount) external onlyReady {
        balances[msg.sender] += amount;
    }
}
"#;

fn dependency_keys(dependencies: &[Dependency]) -> Vec<(String, DependencyKind)> {
    dependencies
        .iter()
        .map(|dependency| (dependency.name.clone(), dependency.kind.clone()))
        .collect()
}

#[test]
fn extracts_structural_inputs_in_one_index() {
    let ast = parse_source(KITCHEN_SINK, Path::new("contracts/Vault.sol")).unwrap();
    let extraction = extract_solidity(&ast);

    assert_eq!(extraction.contracts.len(), 2);
    assert_eq!(extraction.contracts[0].info.name, "Base");
    assert_eq!(extraction.contracts[0].info.state_variables, ["value"]);
    assert_eq!(extraction.contracts[0].info.function_count, 2);
    assert_eq!(extraction.contracts[1].info.name, "Vault");
    assert!(extraction.contracts[1].info.base_classes.is_empty());
    assert!(extraction.dependencies.iter().any(|dependency| {
        dependency.kind == DependencyKind::Inheritance && dependency.name == "Base"
    }));
    assert_eq!(extraction.contracts[1].info.state_variables, ["balances"]);
    assert_eq!(extraction.contracts[1].info.function_count, 4);
    assert_eq!(extraction.contracts[1].advisory_function_count, 3);
    assert_eq!(extraction.callables.len(), 6);
    assert!(extraction.modifier_bodies.contains_key("onlyReady"));
}

#[test]
fn extracts_natspec_signature_inputs() {
    let ast = parse_source(KITCHEN_SINK, Path::new("Vault.sol")).unwrap();
    let extraction = extract_solidity(&ast);
    let read = extraction
        .callables
        .iter()
        .find(|callable| node_text(&callable.node, &ast.source).contains("function read"))
        .unwrap();
    let signature = extraction
        .function_signatures
        .get(&node_line(&read.node))
        .unwrap();

    assert_eq!(signature.params, ["amount"]);
    assert_eq!(signature.return_slots, 1);
}

#[test]
fn extracted_dependencies_match_legacy_extractor() {
    let ast = parse_source(KITCHEN_SINK, Path::new("contracts/Vault.sol")).unwrap();
    let extraction = extract_solidity(&ast);
    let legacy = extract_dependencies(&ast);

    assert_eq!(
        dependency_keys(&extraction.dependencies),
        dependency_keys(&legacy)
    );
}