edb_engine/analysis/
annotation.rs

1// EDB - Ethereum Debugger
2// Copyright (C) 2024 Zhuo Zhang and Wuqi Zhang
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17use foundry_compilers::artifacts::{
18    FunctionDefinition, StateMutability, VariableDeclaration, Visibility,
19};
20
21use crate::{analysis::visitor::VisitorAction, Visitor};
22
23/// Enum representing different types of annotation changes that can be made to Solidity code
24#[derive(Debug, Clone)]
25#[allow(clippy::large_enum_variant)]
26pub enum AnnotationsToChange {
27    /// Change the visibility of a state variable
28    ///
29    /// # Arguments
30    ///
31    /// * `declaration` - The variable declaration to change
32    /// * `visibility` - The new visibility
33    StateVariable {
34        /// The variable declaration to be modified
35        declaration: VariableDeclaration,
36        /// The new visibility level to apply
37        visibility: Visibility,
38    },
39    /// Change the visibility and mutability of a function
40    ///
41    /// # Arguments
42    ///
43    /// * `definition` - The function definition to change
44    /// * `visibility` - The new visibility
45    /// * `mutability` - The new mutability
46    Function {
47        /// The function definition to be modified
48        definition: FunctionDefinition,
49        /// The new visibility level to apply
50        visibility: Visibility,
51        /// The new mutability to apply (None to remove mutability)
52        mutability: Option<StateMutability>,
53    },
54}
55
56/// Analyzer that detects and tracks annotation changes needed for Solidity code
57#[derive(Debug, Clone, Default)]
58pub struct AnnotationAnalyzer {
59    /// List of annotation changes detected during analysis
60    changes: Vec<AnnotationsToChange>,
61}
62
63impl AnnotationAnalyzer {
64    /// Creates a new `AnnotationAnalyzer` instance
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Get the list of annotation changes that were detected during analysis
70    pub fn changes(&self) -> &[AnnotationsToChange] {
71        &self.changes
72    }
73}
74
75impl Visitor for AnnotationAnalyzer {
76    fn visit_variable_declaration(
77        &mut self,
78        declaration: &VariableDeclaration,
79    ) -> eyre::Result<VisitorAction> {
80        if declaration.state_variable {
81            // we need to change the visibility of the state variable to public
82            if declaration.visibility != Visibility::Public {
83                self.changes.push(AnnotationsToChange::StateVariable {
84                    declaration: declaration.clone(),
85                    visibility: Visibility::Public,
86                });
87            }
88        }
89        Ok(VisitorAction::Continue)
90    }
91
92    fn visit_function_definition(
93        &mut self,
94        definition: &FunctionDefinition,
95    ) -> eyre::Result<VisitorAction> {
96        if definition.visibility != Visibility::Public
97            || definition
98                .state_mutability
99                .as_ref()
100                .is_some_and(|mu| *mu == StateMutability::View || *mu == StateMutability::Pure)
101        {
102            self.changes.push(AnnotationsToChange::Function {
103                definition: definition.clone(),
104                visibility: Visibility::Public,
105                mutability: None,
106            });
107        }
108        Ok(VisitorAction::Continue)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use crate::analysis::visitor::Walk;
115    use crate::utils::compile_contract_source_to_source_unit;
116    use semver::Version;
117
118    use super::*;
119
120    #[test]
121    fn test_annotation_analyzer() {
122        let source = r#"
123        contract C {
124            uint256 private stateVar;
125            uint256 public stateVar2;
126            function f() public view {}
127            function f2() public pure {}
128            function f3() public {}
129        }
130        "#;
131        let version = Version::parse("0.8.19").unwrap();
132
133        // Compile the source code to get the AST
134        let source_unit = compile_contract_source_to_source_unit(version, source, true)
135            .expect("Failed to compile contract");
136
137        // Create the analyzer
138        let mut analyzer = AnnotationAnalyzer::new();
139
140        // Walk through the AST using the visitor pattern
141        source_unit.walk(&mut analyzer).expect("Failed to walk AST");
142
143        // Get the changes that were detected
144        let changes = analyzer.changes();
145
146        // Assert that we detected the expected changes
147        // We should have 3 changes:
148        // 1. The private state variable should be changed to public
149        // 2. The view function should be changed to remove view modifier
150        // 3. The pure function should be changed to remove pure modifier
151
152        assert_eq!(changes.len(), 3, "Expected 3 changes, got {}", changes.len());
153
154        // Check that the private state variable is marked for change
155        let state_var_change = changes
156            .iter()
157            .find(|change| matches!(change, AnnotationsToChange::StateVariable { .. }));
158        assert!(state_var_change.is_some(), "Should detect state variable visibility change");
159
160        // Check that the view and pure functions are marked for change
161        let function_changes: Vec<_> = changes
162            .iter()
163            .filter(|change| matches!(change, AnnotationsToChange::Function { .. }))
164            .collect();
165        assert_eq!(function_changes.len(), 2, "Should detect 2 function mutability changes");
166
167        // Verify the specific changes
168        for change in changes {
169            match change {
170                AnnotationsToChange::StateVariable { declaration, visibility } => {
171                    assert_eq!(
172                        visibility,
173                        &Visibility::Public,
174                        "State variable should be changed to public"
175                    );
176                    assert_eq!(
177                        declaration.visibility,
178                        Visibility::Private,
179                        "Original state variable should be private"
180                    );
181                }
182                AnnotationsToChange::Function { definition, visibility, mutability } => {
183                    assert_eq!(
184                        visibility,
185                        &Visibility::Public,
186                        "Function should be changed to public"
187                    );
188                    assert_eq!(mutability, &None, "Function mutability should be removed");
189                    // The function should be either the view or pure function
190                    assert!(
191                        definition.name == "f" || definition.name == "f2",
192                        "Should be view or pure function"
193                    );
194                }
195            }
196        }
197    }
198}