Skip to main content

sentri_analyzer_evm/
ast_types.rs

1//! Type definitions for Solidity AST from solc JSON output.
2//!
3//! These types model the AST structure produced by `solc --combined-json ast`.
4//! They enable precise analysis of control flow, data flow, and vulnerability patterns.
5
6use serde::{Deserialize, Serialize};
7
8/// Parse a "start:length:file" source location string
9pub fn parse_src(src: &str) -> (u64, u64, u64) {
10    let parts: Vec<&str> = src.split(':').collect();
11    let start = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
12    let length = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
13    let file = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
14    (start, length, file)
15}
16
17/// Convert byte offset to line number in source text
18pub fn offset_to_line(source: &str, byte_offset: u64) -> usize {
19    source[..std::cmp::min(byte_offset as usize, source.len())]
20        .chars()
21        .filter(|&c| c == '\n')
22        .count()
23        + 1
24}
25
26/// Source unit (whole contract file)
27#[derive(Debug, Clone, Deserialize, Serialize)]
28pub struct SourceUnit {
29    /// Node ID
30    pub id: u64,
31    /// Source location
32    pub src: String,
33    /// All nodes in this source unit
34    pub nodes: Vec<AstNode>,
35}
36
37/// Contract definition
38#[derive(Debug, Clone, Deserialize, Serialize)]
39pub struct ContractDefinition {
40    /// Node ID
41    pub id: u64,
42    /// Source location
43    pub src: String,
44    /// Contract name
45    pub name: String,
46    /// Kind: contract | interface | library
47    #[serde(rename = "contractKind")]
48    pub contract_kind: String,
49    /// Base contracts
50    #[serde(rename = "baseContracts")]
51    pub base_contracts: Vec<InheritanceSpecifier>,
52    /// All members (functions, state vars, etc.)
53    pub nodes: Vec<AstNode>,
54    /// Linearized base contracts in order
55    #[serde(rename = "linearizedBaseContracts")]
56    pub linearized_base_contracts: Vec<u64>,
57}
58
59/// Base contract reference
60#[derive(Debug, Clone, Deserialize, Serialize)]
61pub struct InheritanceSpecifier {
62    /// Base contract name
63    #[serde(rename = "baseName")]
64    pub base_name: Identifier,
65}
66
67/// Function definition
68#[derive(Debug, Clone, Deserialize, Serialize)]
69pub struct FunctionDefinition {
70    /// Node ID
71    pub id: u64,
72    /// Source location
73    pub src: String,
74    /// Function name
75    pub name: String,
76    /// Visibility: public | external | internal | private
77    pub visibility: String,
78    /// State mutability: pure | view | nonpayable | payable
79    #[serde(rename = "stateMutability")]
80    pub state_mutability: String,
81    /// Is constructor
82    #[serde(rename = "isConstructor")]
83    pub is_constructor: bool,
84    /// Applied modifiers
85    pub modifiers: Vec<ModifierInvocation>,
86    /// Parameters
87    pub parameters: ParameterList,
88    /// Return parameters
89    #[serde(rename = "returnParameters")]
90    pub return_parameters: ParameterList,
91    /// Function body
92    pub body: Option<Block>,
93}
94
95/// Modifier invocation in a function
96#[derive(Debug, Clone, Deserialize, Serialize)]
97pub struct ModifierInvocation {
98    /// Modifier name
99    #[serde(rename = "modifierName")]
100    pub modifier_name: Identifier,
101}
102
103/// Parameter list
104#[derive(Debug, Clone, Deserialize, Serialize)]
105pub struct ParameterList {
106    /// Parameters
107    pub parameters: Vec<VariableDeclaration>,
108}
109
110/// Variable declaration
111#[derive(Debug, Clone, Deserialize, Serialize)]
112pub struct VariableDeclaration {
113    /// Node ID
114    pub id: u64,
115    /// Source location
116    pub src: String,
117    /// Variable name
118    pub name: String,
119    /// Is state variable
120    #[serde(rename = "stateVariable")]
121    pub state_variable: bool,
122    /// Visibility
123    pub visibility: String,
124    /// Storage location: memory | storage | calldata
125    #[serde(rename = "storageLocation")]
126    pub storage_location: String,
127    /// Type descriptions
128    #[serde(rename = "typeName")]
129    pub type_name: Option<Box<AstNode>>,
130    /// Type information
131    #[serde(rename = "typeDescriptions")]
132    pub type_descriptions: Option<TypeDescription>,
133}
134
135/// Type description
136#[derive(Debug, Clone, Deserialize, Serialize)]
137pub struct TypeDescription {
138    /// Type identifier
139    #[serde(rename = "typeIdentifier")]
140    pub type_identifier: String,
141    /// Type string representation
142    #[serde(rename = "typeString")]
143    pub type_string: String,
144}
145
146/// Code block (sequence of statements)
147#[derive(Debug, Clone, Deserialize, Serialize)]
148pub struct Block {
149    /// Node ID
150    pub id: u64,
151    /// Source location
152    pub src: String,
153    /// Statements in order
154    pub statements: Vec<AstNode>,
155}
156
157/// Expression statement
158#[derive(Debug, Clone, Deserialize, Serialize)]
159pub struct ExpressionStatement {
160    /// Node ID
161    pub id: u64,
162    /// Source location
163    pub src: String,
164    /// The expression
165    pub expression: Option<Box<AstNode>>,
166}
167
168/// If statement
169#[derive(Debug, Clone, Deserialize, Serialize)]
170pub struct IfStatement {
171    /// Node ID
172    pub id: u64,
173    /// Source location
174    pub src: String,
175    /// Condition
176    pub condition: Box<AstNode>,
177    /// True branch
178    #[serde(rename = "trueBody")]
179    pub true_body: Box<AstNode>,
180    /// False branch (if exists)
181    #[serde(rename = "falseBody")]
182    pub false_body: Option<Box<AstNode>>,
183}
184
185/// For loop
186#[derive(Debug, Clone, Deserialize, Serialize)]
187pub struct ForStatement {
188    /// Node ID
189    pub id: u64,
190    /// Source location
191    pub src: String,
192}
193
194/// While loop
195#[derive(Debug, Clone, Deserialize, Serialize)]
196pub struct WhileStatement {
197    /// Node ID
198    pub id: u64,
199    /// Source location
200    pub src: String,
201}
202
203/// Return statement
204#[derive(Debug, Clone, Deserialize, Serialize)]
205pub struct ReturnStatement {
206    /// Node ID
207    pub id: u64,
208    /// Source location
209    pub src: String,
210}
211
212/// Assignment
213#[derive(Debug, Clone, Deserialize, Serialize)]
214pub struct Assignment {
215    /// Node ID
216    pub id: u64,
217    /// Source location
218    pub src: String,
219    /// Left-hand side
220    #[serde(rename = "leftHandSide")]
221    pub left_hand_side: Box<AstNode>,
222    /// Operator
223    pub operator: String,
224    /// Right-hand side
225    #[serde(rename = "rightHandSide")]
226    pub right_hand_side: Box<AstNode>,
227}
228
229/// Function call
230#[derive(Debug, Clone, Deserialize, Serialize)]
231pub struct FunctionCall {
232    /// Node ID
233    pub id: u64,
234    /// Source location
235    pub src: String,
236    /// The function expression
237    pub expression: Box<AstNode>,
238    /// Arguments
239    pub arguments: Vec<AstNode>,
240    /// Argument names (for named arguments)
241    pub names: Vec<String>,
242    /// Is try-call
243    #[serde(rename = "tryCall")]
244    pub try_call: bool,
245}
246
247/// Member access (e.g., obj.member)
248#[derive(Debug, Clone, Deserialize, Serialize)]
249pub struct MemberAccess {
250    /// Node ID
251    pub id: u64,
252    /// Source location
253    pub src: String,
254    /// The object being accessed
255    pub expression: Box<AstNode>,
256    /// Member name
257    #[serde(rename = "memberName")]
258    pub member_name: String,
259    /// Type descriptions
260    #[serde(rename = "typeDescriptions")]
261    pub type_descriptions: Option<TypeDescription>,
262}
263
264/// Array/mapping index access (e.g., arr[i])
265#[derive(Debug, Clone, Deserialize, Serialize)]
266pub struct IndexAccess {
267    /// Node ID
268    pub id: u64,
269    /// Source location
270    pub src: String,
271    /// Base expression
272    #[serde(rename = "baseExpression")]
273    pub base_expression: Box<AstNode>,
274    /// Index expression
275    #[serde(rename = "indexExpression")]
276    pub index_expression: Option<Box<AstNode>>,
277}
278
279/// Binary operation
280#[derive(Debug, Clone, Deserialize, Serialize)]
281pub struct BinaryOperation {
282    /// Node ID
283    pub id: u64,
284    /// Source location
285    pub src: String,
286    /// Operator
287    pub operator: String,
288    /// Left operand
289    #[serde(rename = "leftExpression")]
290    pub left_expression: Box<AstNode>,
291    /// Right operand
292    #[serde(rename = "rightExpression")]
293    pub right_expression: Box<AstNode>,
294    /// Common type
295    #[serde(rename = "commonType")]
296    pub common_type: Option<TypeDescription>,
297}
298
299/// Identifier
300#[derive(Debug, Clone, Deserialize, Serialize)]
301pub struct Identifier {
302    /// Node ID
303    pub id: u64,
304    /// Source location
305    pub src: String,
306    /// Name
307    pub name: String,
308    /// Referenced declaration ID
309    #[serde(rename = "referencedDeclaration")]
310    pub referenced_declaration: Option<u64>,
311    /// Type descriptions
312    #[serde(rename = "typeDescriptions")]
313    pub type_descriptions: Option<TypeDescription>,
314}
315
316/// Literal value
317#[derive(Debug, Clone, Deserialize, Serialize)]
318pub struct Literal {
319    /// Node ID
320    pub id: u64,
321    /// Source location
322    pub src: String,
323    /// The value
324    pub value: String,
325    /// Subdenomination (for numbers)
326    pub subdenomination: Option<String>,
327    /// Type descriptions
328    #[serde(rename = "typeDescriptions")]
329    pub type_descriptions: Option<TypeDescription>,
330}
331
332/// Emit statement
333#[derive(Debug, Clone, Deserialize, Serialize)]
334pub struct EmitStatement {
335    /// Node ID
336    pub id: u64,
337    /// Source location
338    pub src: String,
339    /// Event call
340    #[serde(rename = "eventCall")]
341    pub event_call: Box<AstNode>,
342}
343
344/// Revert statement
345#[derive(Debug, Clone, Deserialize, Serialize)]
346pub struct RevertStatement {
347    /// Node ID
348    pub id: u64,
349    /// Source location
350    pub src: String,
351    /// Error call
352    #[serde(rename = "errorCall")]
353    pub error_call: Option<Box<AstNode>>,
354}
355
356/// Modifier definition
357#[derive(Debug, Clone, Deserialize, Serialize)]
358pub struct ModifierDefinition {
359    /// Node ID
360    pub id: u64,
361    /// Source location
362    pub src: String,
363    /// Modifier name
364    pub name: String,
365    /// Parameters
366    pub parameters: ParameterList,
367    /// Body
368    pub body: Option<Block>,
369}
370
371/// State variable declaration
372#[derive(Debug, Clone, Deserialize, Serialize)]
373pub struct StateVariableDeclaration {
374    /// Node ID
375    pub id: u64,
376    /// Source location
377    pub src: String,
378    /// Variables
379    pub variables: Vec<VariableDeclaration>,
380}
381
382/// The main AST node type — a discriminated union using serde tagging
383#[derive(Debug, Clone, Deserialize, Serialize)]
384#[serde(tag = "nodeType")]
385pub enum AstNode {
386    /// Source unit
387    #[serde(rename = "SourceUnit")]
388    SourceUnit(SourceUnit),
389    /// Contract definition
390    #[serde(rename = "ContractDefinition")]
391    ContractDefinition(ContractDefinition),
392    /// Function definition
393    #[serde(rename = "FunctionDefinition")]
394    FunctionDefinition(FunctionDefinition),
395    /// Modifier definition
396    #[serde(rename = "ModifierDefinition")]
397    ModifierDefinition(ModifierDefinition),
398    /// State variable declaration
399    #[serde(rename = "VariableDeclarationStatement")]
400    StateVariableDeclaration(StateVariableDeclaration),
401    /// Expression statement
402    #[serde(rename = "ExpressionStatement")]
403    ExpressionStatement(ExpressionStatement),
404    /// If statement
405    #[serde(rename = "IfStatement")]
406    IfStatement(IfStatement),
407    /// For statement
408    #[serde(rename = "ForStatement")]
409    ForStatement(ForStatement),
410    /// While statement
411    #[serde(rename = "WhileStatement")]
412    WhileStatement(WhileStatement),
413    /// Return statement
414    #[serde(rename = "Return")]
415    ReturnStatement(ReturnStatement),
416    /// Assignment
417    #[serde(rename = "Assignment")]
418    Assignment(Assignment),
419    /// Function call
420    #[serde(rename = "FunctionCall")]
421    FunctionCall(FunctionCall),
422    /// Member access
423    #[serde(rename = "MemberAccess")]
424    MemberAccess(MemberAccess),
425    /// Index access
426    #[serde(rename = "IndexAccess")]
427    IndexAccess(IndexAccess),
428    /// Binary operation
429    #[serde(rename = "BinaryOperation")]
430    BinaryOperation(BinaryOperation),
431    /// Identifier
432    #[serde(rename = "Identifier")]
433    Identifier(Identifier),
434    /// Literal
435    #[serde(rename = "Literal")]
436    Literal(Literal),
437    /// Block
438    #[serde(rename = "Block")]
439    Block(Block),
440    /// Emit statement
441    #[serde(rename = "EmitStatement")]
442    EmitStatement(EmitStatement),
443    /// Revert statement
444    #[serde(rename = "RevertStatement")]
445    RevertStatement(RevertStatement),
446    /// Variable declaration (used in function params, etc.)
447    #[serde(rename = "VariableDeclaration")]
448    VariableDeclaration(VariableDeclaration),
449    /// Catch other node types gracefully
450    #[serde(other)]
451    Other,
452}
453
454impl AstNode {
455    /// Get type string if available
456    pub fn get_type_string(&self) -> Option<String> {
457        match self {
458            AstNode::Identifier(id) => id.type_descriptions.as_ref().map(|t| t.type_string.clone()),
459            AstNode::Literal(lit) => lit
460                .type_descriptions
461                .as_ref()
462                .map(|t| t.type_string.clone()),
463            AstNode::MemberAccess(ma) => {
464                ma.type_descriptions.as_ref().map(|t| t.type_string.clone())
465            }
466            AstNode::BinaryOperation(bo) => bo.common_type.as_ref().map(|t| t.type_string.clone()),
467            _ => None,
468        }
469    }
470}