Testing is key, not only when writing smart contracts but also when writing the detectors to test them.
Testing an Aderyn detector is extremely easy, as it's built in Rust and can be tested as any other Rust code, with the only difference being that you will need a Solidity smart contract implementing the vulnerability your detector has to find.
Luckily, Cyfrin Aderyn has a series of smart contracts in the tests/contracts_playground/src
folder, which you can use to test your detectors.
You can also find the same contracts in the standalone contracts-playground repository.
Now, let's say we want to test the detector we've built in the Detectors quickstart guide, whose only job is ensuring that all modifiers assigned to every function are used and that there are no useless modifiers.
Simplified, the detector looks like this:
pub struct MyFirstDetector { // Keys are source file name and line number found_instances: BTreeMap<(String, usize, String), NodeID>, } impl IssueDetector for MyFirstDetector { fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> { for modifier in context.modifier_definitions() { let mut count = 0; for inv in context.modifier_invocations() { if let Some(id) = inv.modifier_name.referenced_declaration { if id == modifier.id { count += 1; } } } if count == 1 { capture!(self, context, modifier); } } Ok(!self.found_instances.is_empty()) } }
To test it, we will use the OnceModifierExample.sol contract available in the tests/contracts_playground/contracts_playground
folder:
SPDX-License-Identifier: MIT pragma solidity 0.8.19; contract OnceModifierExample { modifier onlyOnce() { _; } function perform() external onlyOnce { } }
As you can notice, the perform()
function has the onlyeOnce
modifier assigned only, which does nothing, our detector should find it and report it - to test this is true, let's copy-paste the following code under our detector declaration:
#[cfg(test)] mod useless_modifier_tests { use crate::detect::detector::{detector_test_helpers::load_contract, IssueDetector}; use super::UselessModifierDetector; #[test] fn test_useless_modifier_tests() { let context = load_contract( "../tests/contract-playground/out/OnceModifierExample.sol/OnceModifierExample.json", ); let mut detector = UselessModifierDetector::default(); // assert that the detector finds the public Function let found = detector.detect(&context).unwrap(); assert!(found); // assert that the detector returns the correct number of instances assert_eq!(detector.instances().len(), 1); // assert that the detector returns the correct severity assert_eq!( detector.severity(), crate::detect::detector::IssueSeverity::Low ); // assert that the detector returns the correct title assert_eq!( detector.title(), String::from("Modifiers invoked only once can be shoe-horned into the function") ); // assert that the detector returns the correct description assert_eq!(detector.description(), String::from("")); } }
Your code should look as follows: https://github.com/Cyfrin/aderyn/blob/dev/aderyn_core/src/detect/low/useless_modifier.rs#L58
In the code above, we are:
Importing the OnceModifierExample.sol smart contract AST
Run the detect function
Assert if we've effectively found the vulnerability
To run the one test we just made, run the following command in your terminal:
cargo test test_useless_modifier_tests
Or, to run all the tests:
cargo test
If the vulnerability was found, the detector successfully worked, and you're ready to use it, publish it, and contribute to Aderyn.
You can learn more about creating tests on Rusts using the official documentation.