Skip to content

Commit

Permalink
Detector: State variable initialized by function (#659)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Roan <[email protected]>
  • Loading branch information
TilakMaddy and alexroan authored Aug 9, 2024
1 parent ff78855 commit 85d18be
Show file tree
Hide file tree
Showing 7 changed files with 546 additions and 10 deletions.
5 changes: 5 additions & 0 deletions aderyn_core/src/detect/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub fn get_all_issue_detectors() -> Vec<Box<dyn IssueDetector>> {
Box::<ContractLocksEtherDetector>::default(),
Box::<IncorrectERC20InterfaceDetector>::default(),
Box::<ReturnBombDetector>::default(),
Box::<FunctionInitializingStateDetector>::default(),
]
}

Expand All @@ -94,6 +95,7 @@ pub fn get_all_detectors_names() -> Vec<String> {
#[derive(Debug, PartialEq, EnumString, Display)]
#[strum(serialize_all = "kebab-case")]
pub(crate) enum IssueDetectorNamePool {
FunctionInitializingState,
DelegateCallInLoop,
CentralizationRisk,
SolmateSafeTransferLib,
Expand Down Expand Up @@ -170,6 +172,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option<Box<dyn Iss
// Expects a valid detector_name
let detector_name = IssueDetectorNamePool::from_str(detector_name).ok()?;
match detector_name {
IssueDetectorNamePool::FunctionInitializingState => {
Some(Box::<FunctionInitializingStateDetector>::default())
}
IssueDetectorNamePool::IncorrectERC20Interface => {
Some(Box::<IncorrectERC20InterfaceDetector>::default())
}
Expand Down
155 changes: 155 additions & 0 deletions aderyn_core/src/detect/low/function_init_state_vars.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use std::collections::BTreeMap;
use std::error::Error;

use crate::ast::{ASTNode, Expression, FunctionCall, Identifier, NodeID};

use crate::capture;
use crate::context::browser::ExtractReferencedDeclarations;
use crate::context::investigator::{
StandardInvestigationStyle, StandardInvestigator, StandardInvestigatorVisitor,
};
use crate::detect::detector::IssueDetectorNamePool;
use crate::{
context::workspace_context::WorkspaceContext,
detect::detector::{IssueDetector, IssueSeverity},
};
use eyre::Result;

#[derive(Default)]
pub struct FunctionInitializingStateDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}

impl IssueDetector for FunctionInitializingStateDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// PLAN
// Capture state variables that are initialized directly by calling a non constant function.
// Go throough state variable declarations with initial value (this will be true when value is set outside constructor)
// See if the function references non-constant state variables. If it does, then capture it

for variable_declaration in context
.variable_declarations()
.into_iter()
.filter(|v| v.state_variable)
{
if let Some(Expression::FunctionCall(FunctionCall { expression, .. })) =
variable_declaration.value.as_ref()
{
if let Expression::Identifier(Identifier {
referenced_declaration: Some(func_id),
..
}) = expression.as_ref()
{
if let Some(ASTNode::FunctionDefinition(func)) = context.nodes.get(func_id) {
let mut tracker =
NonConstantStateVariableReferenceDeclarationTracker::new(context);

let investigator = StandardInvestigator::new(
context,
&[&(func.into())],
StandardInvestigationStyle::Downstream,
)?;

investigator.investigate(context, &mut tracker)?;

if tracker.makes_a_reference {
capture!(self, context, variable_declaration);
}
}
}
}
}

Ok(!self.found_instances.is_empty())
}

fn severity(&self) -> IssueSeverity {
IssueSeverity::Low
}

fn title(&self) -> String {
String::from("Function initializing state.")
}

fn description(&self) -> String {
String::from("Detects the immediate initialization of state variables through function calls that are not pure/constant, or that use \
non-constant state variable. Remove any initialization of state variables via non-constant state variables or function calls. If variables \
must be set upon contract deployment, locate initialization in the constructor instead.")
}

fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}

fn name(&self) -> String {
format!("{}", IssueDetectorNamePool::FunctionInitializingState)
}
}

struct NonConstantStateVariableReferenceDeclarationTracker<'a> {
makes_a_reference: bool,
context: &'a WorkspaceContext,
}

impl<'a> NonConstantStateVariableReferenceDeclarationTracker<'a> {
fn new(context: &'a WorkspaceContext) -> Self {
Self {
makes_a_reference: false,
context,
}
}
}

impl<'a> StandardInvestigatorVisitor for NonConstantStateVariableReferenceDeclarationTracker<'a> {
fn visit_any(&mut self, node: &ASTNode) -> eyre::Result<()> {
// We already know the condition is satisifed
if self.makes_a_reference {
return Ok(());
}

let references = ExtractReferencedDeclarations::from(node).extracted;

for reference in references {
if let Some(ASTNode::VariableDeclaration(variable_declaration)) =
self.context.nodes.get(&reference)
{
if variable_declaration.state_variable && !variable_declaration.constant {
self.makes_a_reference = true;
}
}
}

Ok(())
}
}

#[cfg(test)]
mod function_initializing_state_tests {
use serial_test::serial;

use crate::detect::{
detector::IssueDetector, low::function_init_state_vars::FunctionInitializingStateDetector,
};

#[test]
#[serial]
fn test_function_initializing_state() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/FunctionInitializingState.sol",
);

let mut detector = FunctionInitializingStateDetector::default();
let found = detector.detect(&context).unwrap();
// assert that the detector found an issue
assert!(found);
// assert that the detector found the correct number of instances
assert_eq!(detector.instances().len(), 3);
// assert the severity is low
assert_eq!(
detector.severity(),
crate::detect::detector::IssueSeverity::Low
);
}
}
2 changes: 2 additions & 0 deletions aderyn_core/src/detect/low/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub(crate) mod deprecated_oz_functions;
pub(crate) mod division_before_multiplication;
pub(crate) mod ecrecover;
pub(crate) mod empty_blocks;
pub(crate) mod function_init_state_vars;
pub(crate) mod inconsistent_type_names;
pub(crate) mod large_literal_value;
pub(crate) mod non_reentrant_before_others;
Expand Down Expand Up @@ -37,6 +38,7 @@ pub use deprecated_oz_functions::DeprecatedOZFunctionsDetector;
pub use division_before_multiplication::DivisionBeforeMultiplicationDetector;
pub use ecrecover::EcrecoverDetector;
pub use empty_blocks::EmptyBlockDetector;
pub use function_init_state_vars::FunctionInitializingStateDetector;
pub use inconsistent_type_names::InconsistentTypeNamesDetector;
pub use large_literal_value::LargeLiteralValueDetector;
pub use non_reentrant_before_others::NonReentrantBeforeOthersDetector;
Expand Down
92 changes: 88 additions & 4 deletions reports/report.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"files_summary": {
"total_source_units": 82,
"total_sloc": 2431
"total_source_units": 83,
"total_sloc": 2480
},
"files_details": {
"files_details": [
Expand Down Expand Up @@ -105,6 +105,10 @@
"file_path": "src/ExperimentalEncoder.sol",
"n_sloc": 4
},
{
"file_path": "src/FunctionInitializingState.sol",
"n_sloc": 49
},
{
"file_path": "src/HugeConstants.sol",
"n_sloc": 36
Expand Down Expand Up @@ -337,7 +341,7 @@
},
"issue_count": {
"high": 37,
"low": 29
"low": 30
},
"high_issues": {
"issues": [
Expand Down Expand Up @@ -2360,6 +2364,12 @@
"src": "32:23",
"src_char": "32:23"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 2,
"src": "32:21",
"src_char": "32:21"
},
{
"contract_path": "src/InconsistentUints.sol",
"line_no": 1,
Expand Down Expand Up @@ -2903,6 +2913,54 @@
"src": "457:3",
"src_char": "457:3"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 23,
"src": "790:2",
"src_char": "790:2"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 26,
"src": "820:2",
"src_char": "820:2"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 34,
"src": "1109:2",
"src_char": "1109:2"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 37,
"src": "1139:2",
"src_char": "1139:2"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 42,
"src": "1268:4",
"src_char": "1268:4"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 42,
"src": "1275:4",
"src_char": "1275:4"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 48,
"src": "1459:4",
"src_char": "1459:4"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 48,
"src": "1466:4",
"src_char": "1466:4"
},
{
"contract_path": "src/IncorrectCaretOperator.sol",
"line_no": 17,
Expand Down Expand Up @@ -4514,6 +4572,31 @@
"src_char": "591:115"
}
]
},
{
"title": "Function initializing state.",
"description": "Detects the immediate initialization of state variables through function calls that are not pure/constant, or that use non-constant state variable. Remove any initialization of state variables via non-constant state variables or function calls. If variables must be set upon contract deployment, locate initialization in the constructor instead.",
"detector_name": "function-initializing-state",
"instances": [
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 6,
"src": "108:21",
"src_char": "108:21"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 8,
"src": "199:21",
"src_char": "199:21"
},
{
"contract_path": "src/FunctionInitializingState.sol",
"line_no": 9,
"src": "267:21",
"src_char": "267:21"
}
]
}
]
},
Expand Down Expand Up @@ -4583,6 +4666,7 @@
"msg-value-in-loop",
"contract-locks-ether",
"incorrect-erc20-interface",
"return-bomb"
"return-bomb",
"function-initializing-state"
]
}
Loading

0 comments on commit 85d18be

Please sign in to comment.