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}