1pub mod contracts;
16pub mod enums;
17pub mod events;
18pub mod expressions;
19pub mod functions;
20pub mod source_units;
21pub mod statements;
22pub mod types;
23pub mod variables;
24#[cfg(test)]
25pub mod visitor;
26pub mod yul;
27
28pub use contracts::*;
30pub use enums::*;
31pub use events::*;
32pub use expressions::*;
33pub use functions::*;
34pub use source_units::*;
35pub use statements::*;
36pub use types::*;
37pub use variables::*;
38#[cfg(test)]
39pub use visitor::*;
40pub use yul::*;
41
42use serde::{Deserialize, Serialize};
43use std::collections::HashMap;
44
45#[derive(Clone, Debug)]
56pub enum DeclNode {
57 FunctionDefinition(FunctionDefinition),
58 VariableDeclaration(VariableDeclaration),
59 ContractDefinition(ContractDefinition),
60 EventDefinition(EventDefinition),
61 ErrorDefinition(ErrorDefinition),
62 StructDefinition(StructDefinition),
63 EnumDefinition(EnumDefinition),
64 ModifierDefinition(ModifierDefinition),
65 UserDefinedValueTypeDefinition(UserDefinedValueTypeDefinition),
66}
67
68impl DeclNode {
69 pub fn id(&self) -> NodeID {
71 match self {
72 Self::FunctionDefinition(n) => n.id,
73 Self::VariableDeclaration(n) => n.id,
74 Self::ContractDefinition(n) => n.id,
75 Self::EventDefinition(n) => n.id,
76 Self::ErrorDefinition(n) => n.id,
77 Self::StructDefinition(n) => n.id,
78 Self::EnumDefinition(n) => n.id,
79 Self::ModifierDefinition(n) => n.id,
80 Self::UserDefinedValueTypeDefinition(n) => n.id,
81 }
82 }
83
84 pub fn name(&self) -> &str {
86 match self {
87 Self::FunctionDefinition(n) => &n.name,
88 Self::VariableDeclaration(n) => &n.name,
89 Self::ContractDefinition(n) => &n.name,
90 Self::EventDefinition(n) => &n.name,
91 Self::ErrorDefinition(n) => &n.name,
92 Self::StructDefinition(n) => &n.name,
93 Self::EnumDefinition(n) => &n.name,
94 Self::ModifierDefinition(n) => &n.name,
95 Self::UserDefinedValueTypeDefinition(n) => &n.name,
96 }
97 }
98
99 pub fn src(&self) -> &str {
101 match self {
102 Self::FunctionDefinition(n) => &n.src,
103 Self::VariableDeclaration(n) => &n.src,
104 Self::ContractDefinition(n) => &n.src,
105 Self::EventDefinition(n) => &n.src,
106 Self::ErrorDefinition(n) => &n.src,
107 Self::StructDefinition(n) => &n.src,
108 Self::EnumDefinition(n) => &n.src,
109 Self::ModifierDefinition(n) => &n.src,
110 Self::UserDefinedValueTypeDefinition(n) => &n.src,
111 }
112 }
113
114 pub fn scope(&self) -> Option<NodeID> {
116 match self {
117 Self::FunctionDefinition(n) => n.scope,
118 Self::VariableDeclaration(n) => n.scope,
119 Self::ContractDefinition(n) => n.scope,
120 Self::EventDefinition(_) => None,
121 Self::ErrorDefinition(_) => None,
122 Self::StructDefinition(n) => n.scope,
123 Self::EnumDefinition(_) => None,
124 Self::ModifierDefinition(_) => None,
125 Self::UserDefinedValueTypeDefinition(_) => None,
126 }
127 }
128
129 pub fn base_functions(&self) -> Option<&[NodeID]> {
134 match self {
135 Self::FunctionDefinition(n) => n.base_functions.as_deref(),
136 Self::ModifierDefinition(n) => n.base_modifiers.as_deref(),
137 Self::VariableDeclaration(n) => n.base_functions.as_deref(),
138 _ => None,
139 }
140 }
141
142 pub fn documentation(&self) -> Option<&Documentation> {
144 match self {
145 Self::FunctionDefinition(n) => n.documentation.as_ref(),
146 Self::VariableDeclaration(n) => n.documentation.as_ref(),
147 Self::ContractDefinition(n) => n.documentation.as_ref(),
148 Self::EventDefinition(n) => n.documentation.as_ref(),
149 Self::ErrorDefinition(n) => n.documentation.as_ref(),
150 Self::StructDefinition(n) => n.documentation.as_ref(),
151 Self::EnumDefinition(n) => n.documentation.as_ref(),
152 Self::ModifierDefinition(n) => n.documentation.as_ref(),
153 Self::UserDefinedValueTypeDefinition(_) => None,
154 }
155 }
156
157 pub fn selector(&self) -> Option<&str> {
159 match self {
160 Self::FunctionDefinition(n) => n.function_selector.as_deref(),
161 Self::VariableDeclaration(n) => n.function_selector.as_deref(),
162 Self::EventDefinition(n) => n.event_selector.as_deref(),
163 Self::ErrorDefinition(n) => n.error_selector.as_deref(),
164 _ => None,
165 }
166 }
167
168 pub fn extract_typed_selector(&self) -> Option<crate::types::Selector> {
173 match self {
174 Self::FunctionDefinition(n) => n
175 .function_selector
176 .as_deref()
177 .map(|s| crate::types::Selector::Func(crate::types::FuncSelector::new(s))),
178 Self::VariableDeclaration(n) => n
179 .function_selector
180 .as_deref()
181 .map(|s| crate::types::Selector::Func(crate::types::FuncSelector::new(s))),
182 Self::ErrorDefinition(n) => n
183 .error_selector
184 .as_deref()
185 .map(|s| crate::types::Selector::Func(crate::types::FuncSelector::new(s))),
186 Self::EventDefinition(n) => n
187 .event_selector
188 .as_deref()
189 .map(|s| crate::types::Selector::Event(crate::types::EventSelector::new(s))),
190 _ => None,
191 }
192 }
193
194 pub fn extract_doc_text(&self) -> Option<String> {
198 self.documentation().map(|doc| match doc {
199 Documentation::String(s) => s.clone(),
200 Documentation::Structured(s) => s.text.clone(),
201 })
202 }
203
204 pub fn build_signature(&self) -> Option<String> {
209 match self {
210 Self::FunctionDefinition(n) => {
211 let params = format_params_typed(&n.parameters);
212 let returns = format_params_typed(&n.return_parameters);
213
214 let mut sig = match n.kind {
215 FunctionKind::Constructor => format!("constructor({params})"),
216 FunctionKind::Receive => "receive() external payable".to_string(),
217 FunctionKind::Fallback => format!("fallback({params})"),
218 _ => format!("function {}({params})", n.name),
219 };
220
221 if !matches!(n.kind, FunctionKind::Constructor | FunctionKind::Receive)
223 && let Some(vis) = &n.visibility
224 {
225 let vis_str = vis.to_string();
226 if !vis_str.is_empty() {
227 sig.push_str(&format!(" {vis_str}"));
228 }
229 }
230
231 if !matches!(n.state_mutability, StateMutability::Nonpayable) {
233 sig.push_str(&format!(" {}", n.state_mutability));
234 }
235
236 if !returns.is_empty() {
237 sig.push_str(&format!(" returns ({returns})"));
238 }
239 Some(sig)
240 }
241 Self::ModifierDefinition(n) => {
242 let params = format_params_typed(&n.parameters);
243 Some(format!("modifier {}({params})", n.name))
244 }
245 Self::EventDefinition(n) => {
246 let params = format_params_typed(&n.parameters);
247 Some(format!("event {}({params})", n.name))
248 }
249 Self::ErrorDefinition(n) => {
250 let params = format_params_typed(&n.parameters);
251 Some(format!("error {}({params})", n.name))
252 }
253 Self::VariableDeclaration(n) => {
254 let type_str = n
255 .type_descriptions
256 .type_string
257 .as_deref()
258 .unwrap_or("unknown");
259 let vis = n
260 .visibility
261 .as_ref()
262 .map(|v| v.to_string())
263 .unwrap_or_default();
264
265 let mut sig = type_str.to_string();
266 if !vis.is_empty() {
267 sig.push_str(&format!(" {vis}"));
268 }
269 match &n.mutability {
270 Some(Mutability::Constant) => sig.push_str(" constant"),
271 Some(Mutability::Immutable) => sig.push_str(" immutable"),
272 _ => {}
273 }
274 sig.push_str(&format!(" {}", n.name));
275 Some(sig)
276 }
277 Self::ContractDefinition(n) => {
278 let mut sig = format!("{} {}", n.contract_kind, n.name);
279
280 if !n.base_contracts.is_empty() {
281 let base_names: Vec<&str> = n
282 .base_contracts
283 .iter()
284 .map(|b| b.base_name.name.as_str())
285 .collect();
286 if !base_names.is_empty() {
287 sig.push_str(&format!(" is {}", base_names.join(", ")));
288 }
289 }
290 Some(sig)
291 }
292 Self::StructDefinition(n) => {
293 let mut sig = format!("struct {} {{\n", n.name);
294 for member in &n.members {
295 let mtype = member
296 .type_descriptions
297 .type_string
298 .as_deref()
299 .unwrap_or("?");
300 sig.push_str(&format!(" {mtype} {};\n", member.name));
301 }
302 sig.push('}');
303 Some(sig)
304 }
305 Self::EnumDefinition(n) => {
306 let mut sig = format!("enum {} {{\n", n.name);
307 for member in &n.members {
308 sig.push_str(&format!(" {},\n", member.name));
309 }
310 sig.push('}');
311 Some(sig)
312 }
313 Self::UserDefinedValueTypeDefinition(n) => {
314 let underlying = type_name_to_str(&n.underlying_type);
315 Some(format!("type {} is {underlying}", n.name))
316 }
317 }
318 }
319
320 pub fn param_strings(&self) -> Vec<String> {
325 match self {
326 Self::FunctionDefinition(n) => build_param_strings_typed(&n.parameters),
327 Self::ModifierDefinition(n) => build_param_strings_typed(&n.parameters),
328 Self::EventDefinition(n) => build_param_strings_typed(&n.parameters),
329 Self::ErrorDefinition(n) => build_param_strings_typed(&n.parameters),
330 _ => Vec::new(),
331 }
332 }
333
334 pub fn parameters(&self) -> Option<&ParameterList> {
336 match self {
337 Self::FunctionDefinition(n) => Some(&n.parameters),
338 Self::ModifierDefinition(n) => Some(&n.parameters),
339 Self::EventDefinition(n) => Some(&n.parameters),
340 Self::ErrorDefinition(n) => Some(&n.parameters),
341 _ => None,
342 }
343 }
344
345 pub fn return_parameters(&self) -> Option<&ParameterList> {
347 match self {
348 Self::FunctionDefinition(n) => Some(&n.return_parameters),
349 _ => None,
350 }
351 }
352
353 pub fn node_type(&self) -> &'static str {
355 match self {
356 Self::FunctionDefinition(_) => "FunctionDefinition",
357 Self::VariableDeclaration(_) => "VariableDeclaration",
358 Self::ContractDefinition(_) => "ContractDefinition",
359 Self::EventDefinition(_) => "EventDefinition",
360 Self::ErrorDefinition(_) => "ErrorDefinition",
361 Self::StructDefinition(_) => "StructDefinition",
362 Self::EnumDefinition(_) => "EnumDefinition",
363 Self::ModifierDefinition(_) => "ModifierDefinition",
364 Self::UserDefinedValueTypeDefinition(_) => "UserDefinedValueTypeDefinition",
365 }
366 }
367
368 pub fn type_string(&self) -> Option<&str> {
370 match self {
371 Self::VariableDeclaration(n) => n.type_descriptions.type_string.as_deref(),
372 _ => None,
373 }
374 }
375
376 pub fn param_names(&self) -> Option<Vec<String>> {
382 match self {
383 Self::FunctionDefinition(n) => Some(
384 n.parameters
385 .parameters
386 .iter()
387 .map(|p| p.name.clone())
388 .collect(),
389 ),
390 Self::ModifierDefinition(n) => Some(
391 n.parameters
392 .parameters
393 .iter()
394 .map(|p| p.name.clone())
395 .collect(),
396 ),
397 Self::EventDefinition(n) => Some(
398 n.parameters
399 .parameters
400 .iter()
401 .map(|p| p.name.clone())
402 .collect(),
403 ),
404 Self::ErrorDefinition(n) => Some(
405 n.parameters
406 .parameters
407 .iter()
408 .map(|p| p.name.clone())
409 .collect(),
410 ),
411 Self::StructDefinition(n) => Some(n.members.iter().map(|m| m.name.clone()).collect()),
412 _ => None,
413 }
414 }
415
416 pub fn is_constructor(&self) -> bool {
418 matches!(self, Self::FunctionDefinition(n) if matches!(n.kind, crate::solc_ast::enums::FunctionKind::Constructor))
419 }
420}
421
422pub fn format_params_typed(params: &ParameterList) -> String {
428 let parts: Vec<String> = params
429 .parameters
430 .iter()
431 .map(|p| {
432 let type_str = p.type_descriptions.type_string.as_deref().unwrap_or("?");
433 let name = &p.name;
434 let storage = p
435 .storage_location
436 .as_ref()
437 .map(|s| s.to_string())
438 .unwrap_or_else(|| "default".to_string());
439
440 if name.is_empty() {
441 type_str.to_string()
442 } else if storage != "default" {
443 format!("{type_str} {storage} {name}")
444 } else {
445 format!("{type_str} {name}")
446 }
447 })
448 .collect();
449
450 parts.join(", ")
451}
452
453fn build_param_strings_typed(params: &ParameterList) -> Vec<String> {
457 params
458 .parameters
459 .iter()
460 .map(|p| {
461 let type_str = p.type_descriptions.type_string.as_deref().unwrap_or("?");
462 let name = &p.name;
463 let storage = p
464 .storage_location
465 .as_ref()
466 .map(|s| s.to_string())
467 .unwrap_or_else(|| "default".to_string());
468
469 if name.is_empty() {
470 type_str.to_string()
471 } else if storage != "default" {
472 format!("{type_str} {storage} {name}")
473 } else {
474 format!("{type_str} {name}")
475 }
476 })
477 .collect()
478}
479
480pub fn type_name_to_str(tn: &TypeName) -> &str {
482 match tn {
483 TypeName::ElementaryTypeName(e) => e
484 .type_descriptions
485 .type_string
486 .as_deref()
487 .unwrap_or(&e.name),
488 TypeName::UserDefinedTypeName(u) => u
489 .type_descriptions
490 .type_string
491 .as_deref()
492 .unwrap_or("unknown"),
493 TypeName::FunctionTypeName(f) => f
494 .type_descriptions
495 .type_string
496 .as_deref()
497 .unwrap_or("function"),
498 TypeName::Mapping(m) => m
499 .type_descriptions
500 .type_string
501 .as_deref()
502 .unwrap_or("mapping"),
503 TypeName::ArrayTypeName(a) => a
504 .type_descriptions
505 .type_string
506 .as_deref()
507 .unwrap_or("array"),
508 }
509}
510
511const DECL_NODE_TYPES: &[&str] = &[
515 "FunctionDefinition",
516 "VariableDeclaration",
517 "ContractDefinition",
518 "EventDefinition",
519 "ErrorDefinition",
520 "StructDefinition",
521 "EnumDefinition",
522 "ModifierDefinition",
523 "UserDefinedValueTypeDefinition",
524];
525
526const STRIP_FIELDS: &[&str] = &[
530 "body",
531 "modifiers",
532 "value",
533 "overrides",
534 "nameLocation",
535 "implemented",
536 "isVirtual",
537 "abstract",
538 "contractDependencies",
539 "usedErrors",
540 "usedEvents",
541 "fullyImplemented",
542 "linearizedBaseContracts",
543 "canonicalName",
544 "constant",
545 "indexed",
546];
547
548const STRIP_CHILD_FIELDS: &[&str] = &[
551 "body",
552 "modifiers",
553 "value",
554 "overrides",
555 "nameLocation",
556 "implemented",
557 "isVirtual",
558 "constant",
559 "indexed",
560 "canonicalName",
561];
562
563pub struct ExtractedDecls {
565 pub decl_index: HashMap<NodeID, DeclNode>,
567 pub node_id_to_source_path: HashMap<NodeID, String>,
569}
570
571pub fn extract_decl_nodes(sources: &serde_json::Value) -> Option<ExtractedDecls> {
589 let sources_obj = sources.as_object()?;
590 let source_count = sources_obj.len();
593 let mut decl_index = HashMap::with_capacity(source_count * 32);
594 let mut id_to_path = HashMap::with_capacity(source_count * 32);
595
596 for (path, source_data) in sources_obj {
597 let ast_node = source_data.get("ast")?;
598
599 if let Some(su_id) = ast_node.get("id").and_then(|v| v.as_i64()) {
601 id_to_path.insert(su_id, path.clone());
602 }
603
604 if let Some(nodes) = ast_node.get("nodes").and_then(|v| v.as_array()) {
606 for node in nodes {
607 walk_and_extract(node, path, &mut decl_index, &mut id_to_path);
608 }
609 }
610 }
611
612 Some(ExtractedDecls {
613 decl_index,
614 node_id_to_source_path: id_to_path,
615 })
616}
617
618fn walk_and_extract(
620 node: &serde_json::Value,
621 source_path: &str,
622 decl_index: &mut HashMap<NodeID, DeclNode>,
623 id_to_path: &mut HashMap<NodeID, String>,
624) {
625 let obj = match node.as_object() {
626 Some(o) => o,
627 None => return,
628 };
629
630 let node_type = match obj.get("nodeType").and_then(|v| v.as_str()) {
631 Some(nt) => nt,
632 None => return,
633 };
634
635 let node_id = obj.get("id").and_then(|v| v.as_i64());
636
637 if let Some(id) = node_id {
639 id_to_path.insert(id, source_path.to_string());
640 }
641
642 if DECL_NODE_TYPES.contains(&node_type)
644 && let Some(id) = node_id
645 {
646 let node_value = if node_type == "ContractDefinition" {
650 build_filtered_contract(obj)
651 } else {
652 build_filtered_decl(obj)
653 };
654
655 if let Some(decl) = deserialize_decl_node(node_type, node_value) {
657 decl_index.insert(id, decl);
658 }
659 }
660
661 if let Some(children) = obj.get("nodes").and_then(|v| v.as_array()) {
666 for child in children {
667 walk_and_extract(child, source_path, decl_index, id_to_path);
668 }
669 }
670
671 for param_key in &["parameters", "returnParameters"] {
674 if let Some(param_list) = obj.get(*param_key).and_then(|v| v.as_object()) {
675 if let Some(pl_id) = param_list.get("id").and_then(|v| v.as_i64()) {
677 id_to_path.insert(pl_id, source_path.to_string());
678 }
679 if let Some(params) = param_list.get("parameters").and_then(|v| v.as_array()) {
680 for param in params {
681 walk_and_extract(param, source_path, decl_index, id_to_path);
682 }
683 }
684 }
685 }
686
687 if let Some(members) = obj.get("members").and_then(|v| v.as_array()) {
689 for member in members {
690 walk_and_extract(member, source_path, decl_index, id_to_path);
691 }
692 }
693}
694
695fn build_filtered_decl(obj: &serde_json::Map<String, serde_json::Value>) -> serde_json::Value {
700 let mut filtered = serde_json::Map::with_capacity(obj.len());
701 for (key, value) in obj {
702 if !STRIP_FIELDS.contains(&key.as_str()) {
703 filtered.insert(key.clone(), value.clone());
704 }
705 }
706 serde_json::Value::Object(filtered)
707}
708
709fn build_filtered_contract(obj: &serde_json::Map<String, serde_json::Value>) -> serde_json::Value {
715 let mut filtered = serde_json::Map::with_capacity(obj.len());
716 for (key, value) in obj {
717 if STRIP_FIELDS.contains(&key.as_str()) {
718 continue;
719 }
720 if key == "nodes" {
721 if let Some(arr) = value.as_array() {
723 let filtered_nodes: Vec<serde_json::Value> = arr
724 .iter()
725 .map(|child| {
726 if let Some(child_obj) = child.as_object() {
727 let mut filtered_child =
728 serde_json::Map::with_capacity(child_obj.len());
729 for (ck, cv) in child_obj {
730 if !STRIP_CHILD_FIELDS.contains(&ck.as_str()) {
731 filtered_child.insert(ck.clone(), cv.clone());
732 }
733 }
734 serde_json::Value::Object(filtered_child)
735 } else {
736 child.clone()
737 }
738 })
739 .collect();
740 filtered.insert(key.clone(), serde_json::Value::Array(filtered_nodes));
741 } else {
742 filtered.insert(key.clone(), value.clone());
743 }
744 } else {
745 filtered.insert(key.clone(), value.clone());
746 }
747 }
748 serde_json::Value::Object(filtered)
749}
750
751fn deserialize_decl_node(node_type: &str, value: serde_json::Value) -> Option<DeclNode> {
753 match node_type {
754 "FunctionDefinition" => serde_json::from_value::<FunctionDefinition>(value)
755 .ok()
756 .map(DeclNode::FunctionDefinition),
757 "VariableDeclaration" => serde_json::from_value::<VariableDeclaration>(value)
758 .ok()
759 .map(DeclNode::VariableDeclaration),
760 "ContractDefinition" => serde_json::from_value::<ContractDefinition>(value)
761 .ok()
762 .map(DeclNode::ContractDefinition),
763 "EventDefinition" => serde_json::from_value::<EventDefinition>(value)
764 .ok()
765 .map(DeclNode::EventDefinition),
766 "ErrorDefinition" => serde_json::from_value::<ErrorDefinition>(value)
767 .ok()
768 .map(DeclNode::ErrorDefinition),
769 "StructDefinition" => serde_json::from_value::<StructDefinition>(value)
770 .ok()
771 .map(DeclNode::StructDefinition),
772 "EnumDefinition" => serde_json::from_value::<EnumDefinition>(value)
773 .ok()
774 .map(DeclNode::EnumDefinition),
775 "ModifierDefinition" => serde_json::from_value::<ModifierDefinition>(value)
776 .ok()
777 .map(DeclNode::ModifierDefinition),
778 "UserDefinedValueTypeDefinition" => {
779 serde_json::from_value::<UserDefinedValueTypeDefinition>(value)
780 .ok()
781 .map(DeclNode::UserDefinedValueTypeDefinition)
782 }
783 _ => None,
784 }
785}
786
787pub type NodeID = i64;
791
792#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
796#[serde(rename_all = "camelCase")]
797pub struct TypeDescriptions {
798 pub type_string: Option<String>,
800 pub type_identifier: Option<String>,
802}
803
804#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
808#[serde(untagged)]
809pub enum Documentation {
810 String(String),
812 Structured(StructuredDocumentation),
814}
815
816#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
818#[serde(rename_all = "camelCase")]
819pub struct StructuredDocumentation {
820 pub id: NodeID,
821 pub src: String,
822 pub text: String,
823}
824
825#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
829#[serde(rename_all = "camelCase")]
830pub struct ExternalReference {
831 pub declaration: NodeID,
832 #[serde(default)]
833 pub is_offset: bool,
834 #[serde(default)]
835 pub is_slot: bool,
836 pub src: String,
837 #[serde(default)]
838 pub suffix: Option<String>,
839 pub value_size: Option<i64>,
840}
841
842#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
847#[serde(rename_all = "camelCase")]
848pub struct UsingForFunction {
849 pub function: Option<IdentifierPath>,
851 pub definition: Option<IdentifierPath>,
853 pub operator: Option<String>,
855}
856
857#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
859#[serde(rename_all = "camelCase")]
860pub struct IdentifierPath {
861 pub id: NodeID,
862 pub name: String,
863 #[serde(default)]
864 pub name_locations: Vec<String>,
865 pub referenced_declaration: Option<NodeID>,
866 pub src: String,
867}
868
869#[cfg(test)]
872mod tests {
873 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
877 #[serde(rename_all = "camelCase")]
878 pub struct SolcOutput {
879 #[serde(default)]
880 pub sources: std::collections::HashMap<String, SourceEntry>,
881 #[serde(default)]
882 pub source_id_to_path: std::collections::HashMap<String, String>,
883 }
884
885 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
887 pub struct SourceEntry {
888 pub id: i64,
889 pub ast: super::SourceUnit,
890 }
891 use super::*;
892 use std::path::Path;
893
894 fn load_fixture() -> SolcOutput {
896 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
897 let json = std::fs::read_to_string(&path)
898 .unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
899 serde_json::from_str(&json)
900 .unwrap_or_else(|e| panic!("failed to deserialize poolmanager.json: {e}"))
901 }
902
903 #[test]
904 fn deserialize_poolmanager() {
905 let output = load_fixture();
906
907 assert_eq!(
909 output.sources.len(),
910 45,
911 "expected 45 source files in poolmanager.json"
912 );
913
914 for (path, entry) in &output.sources {
916 assert!(entry.ast.id > 0, "bad AST id for {path}");
917 assert!(!entry.ast.src.is_empty(), "empty src for {path}");
918 }
919 }
920
921 #[test]
922 fn deserialize_all_source_units() {
923 let output = load_fixture();
924
925 let mut contract_count = 0;
928 for (_path, entry) in output.sources {
929 for node in &entry.ast.nodes {
930 if matches!(node, SourceUnitNode::ContractDefinition(_)) {
931 contract_count += 1;
932 }
933 }
934 }
935 assert_eq!(
936 contract_count, 43,
937 "expected 43 top-level ContractDefinitions"
938 );
939 }
940
941 struct NodeCounter {
945 functions: usize,
946 contracts: usize,
947 events: usize,
948 errors: usize,
949 variables: usize,
950 total_nodes: usize,
951 }
952
953 impl NodeCounter {
954 fn new() -> Self {
955 Self {
956 functions: 0,
957 contracts: 0,
958 events: 0,
959 errors: 0,
960 variables: 0,
961 total_nodes: 0,
962 }
963 }
964 }
965
966 impl AstVisitor for NodeCounter {
967 fn visit_node(&mut self, _id: NodeID, _src: &str) -> bool {
968 self.total_nodes += 1;
969 true
970 }
971
972 fn visit_function_definition(&mut self, node: &FunctionDefinition) -> bool {
973 self.functions += 1;
974 self.visit_node(node.id, &node.src)
975 }
976
977 fn visit_contract_definition(&mut self, node: &ContractDefinition) -> bool {
978 self.contracts += 1;
979 self.visit_node(node.id, &node.src)
980 }
981
982 fn visit_event_definition(&mut self, node: &EventDefinition) -> bool {
983 self.events += 1;
984 self.visit_node(node.id, &node.src)
985 }
986
987 fn visit_error_definition(&mut self, node: &ErrorDefinition) -> bool {
988 self.errors += 1;
989 self.visit_node(node.id, &node.src)
990 }
991
992 fn visit_variable_declaration(&mut self, node: &VariableDeclaration) -> bool {
993 self.variables += 1;
994 self.visit_node(node.id, &node.src)
995 }
996 }
997
998 #[test]
999 fn visitor_counts_nodes() {
1000 let output = load_fixture();
1001 let mut counter = NodeCounter::new();
1002
1003 for (_path, entry) in output.sources {
1004 entry.ast.accept(&mut counter);
1005 }
1006
1007 assert_eq!(counter.contracts, 43, "expected 43 ContractDefinitions");
1008 assert_eq!(counter.functions, 215, "expected 215 FunctionDefinitions");
1009 assert_eq!(counter.events, 12, "expected 12 EventDefinitions");
1010 assert_eq!(counter.errors, 42, "expected 42 ErrorDefinitions");
1011 assert!(
1012 counter.variables > 0,
1013 "expected at least some VariableDeclarations"
1014 );
1015 assert!(
1016 counter.total_nodes > 0,
1017 "expected total_nodes to be non-zero"
1018 );
1019
1020 let specific_sum = counter.contracts + counter.functions + counter.events + counter.errors;
1022 assert!(
1023 counter.total_nodes > specific_sum,
1024 "total_nodes ({}) should be > sum of specific types ({specific_sum})",
1025 counter.total_nodes
1026 );
1027 }
1028
1029 #[test]
1030 fn cached_build_populates_decl_index() {
1031 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
1032 let json = std::fs::read_to_string(&path).unwrap();
1033 let raw: serde_json::Value = serde_json::from_str(&json).unwrap();
1034
1035 let build = crate::goto::CachedBuild::new(raw, 0, None);
1036
1037 assert!(
1038 !build.decl_index.is_empty(),
1039 "decl_index should be populated"
1040 );
1041
1042 let mut funcs = 0;
1044 let mut vars = 0;
1045 let mut contracts = 0;
1046 let mut events = 0;
1047 let mut errors = 0;
1048 for decl in build.decl_index.values() {
1049 match decl {
1050 DeclNode::FunctionDefinition(_) => funcs += 1,
1051 DeclNode::VariableDeclaration(_) => vars += 1,
1052 DeclNode::ContractDefinition(_) => contracts += 1,
1053 DeclNode::EventDefinition(_) => events += 1,
1054 DeclNode::ErrorDefinition(_) => errors += 1,
1055 _ => {}
1056 }
1057 }
1058
1059 assert_eq!(
1060 contracts, 43,
1061 "expected 43 ContractDefinitions in decl_index"
1062 );
1063 assert_eq!(funcs, 215, "expected 215 FunctionDefinitions in decl_index");
1064 assert_eq!(events, 12, "expected 12 EventDefinitions in decl_index");
1065 assert_eq!(errors, 42, "expected 42 ErrorDefinitions in decl_index");
1066 assert!(vars > 0, "expected VariableDeclarations in decl_index");
1067 }
1068
1069 #[test]
1071 fn node_id_to_source_path_covers_decl_index() {
1072 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
1073 let json = std::fs::read_to_string(&path).unwrap();
1074 let raw: serde_json::Value = serde_json::from_str(&json).unwrap();
1075
1076 let build = crate::goto::CachedBuild::new(raw, 0, None);
1077
1078 assert!(
1079 !build.node_id_to_source_path.is_empty(),
1080 "node_id_to_source_path should be populated"
1081 );
1082
1083 let mut missing = Vec::new();
1085 for (id, decl) in &build.decl_index {
1086 if matches!(decl, DeclNode::VariableDeclaration(v) if v.state_variable != Some(true)) {
1089 continue;
1090 }
1091 if !build.node_id_to_source_path.contains_key(id) {
1092 missing.push(format!(
1093 "id={id} name={:?} type={}",
1094 decl.name(),
1095 decl.node_type()
1096 ));
1097 }
1098 }
1099
1100 assert!(
1101 missing.is_empty(),
1102 "Declarations without source path ({}):\n{}",
1103 missing.len(),
1104 missing.join("\n"),
1105 );
1106 }
1107
1108 #[test]
1110 fn base_functions_populated_on_decl_nodes() {
1111 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
1112 let json = std::fs::read_to_string(&path).unwrap();
1113 let raw: serde_json::Value = serde_json::from_str(&json).unwrap();
1114
1115 let build = crate::goto::CachedBuild::new(raw, 0, None);
1116
1117 let swap_decl = build
1119 .decl_index
1120 .get(&crate::types::NodeId(616))
1121 .expect("PoolManager.swap (id 616) should exist in decl_index");
1122 assert_eq!(swap_decl.name(), "swap");
1123 let base = swap_decl
1124 .base_functions()
1125 .expect("swap should have baseFunctions");
1126 assert_eq!(base, &[2036], "swap baseFunctions should be [2036]");
1127
1128 let iswap_decl = build
1130 .decl_index
1131 .get(&crate::types::NodeId(2036))
1132 .expect("IPoolManager.swap (id 2036) should exist in decl_index");
1133 assert_eq!(iswap_decl.name(), "swap");
1134 assert!(
1135 iswap_decl.base_functions().is_none()
1136 || iswap_decl.base_functions().unwrap().is_empty(),
1137 "IPoolManager.swap should have no baseFunctions"
1138 );
1139
1140 let with_base: Vec<_> = build
1142 .decl_index
1143 .values()
1144 .filter(|d| d.base_functions().is_some_and(|b| !b.is_empty()))
1145 .collect();
1146 assert_eq!(
1147 with_base.len(),
1148 32,
1149 "expected 32 declarations with baseFunctions/baseModifiers in poolmanager.json"
1150 );
1151 }
1152
1153 #[test]
1155 fn base_function_implementation_index_populated() {
1156 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
1157 let json = std::fs::read_to_string(&path).unwrap();
1158 let raw: serde_json::Value = serde_json::from_str(&json).unwrap();
1159
1160 let build = crate::goto::CachedBuild::new(raw, 0, None);
1161
1162 let forward = build
1164 .base_function_implementation
1165 .get(&crate::types::NodeId(616))
1166 .expect("616 should be in base_function_implementation");
1167 assert!(
1168 forward.contains(&crate::types::NodeId(2036)),
1169 "616 → 2036 mapping should exist"
1170 );
1171
1172 let reverse = build
1174 .base_function_implementation
1175 .get(&crate::types::NodeId(2036))
1176 .expect("2036 should be in base_function_implementation");
1177 assert!(
1178 reverse.contains(&crate::types::NodeId(616)),
1179 "2036 → 616 mapping should exist"
1180 );
1181
1182 assert!(
1184 !build.base_function_implementation.is_empty(),
1185 "base_function_implementation should be populated"
1186 );
1187
1188 let init_forward = build
1190 .base_function_implementation
1191 .get(&crate::types::NodeId(330))
1192 .expect("330 should be in base_function_implementation");
1193 assert!(
1194 init_forward.contains(&crate::types::NodeId(2003)),
1195 "330 → 2003 mapping should exist"
1196 );
1197 }
1198
1199 #[test]
1202 fn base_function_implementation_survives_warm_load() {
1203 let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("poolmanager.json");
1204 let json = std::fs::read_to_string(&path).unwrap();
1205 let raw: serde_json::Value = serde_json::from_str(&json).unwrap();
1206
1207 let fresh_build = crate::goto::CachedBuild::new(raw, 0, None);
1209
1210 let warm_build = crate::goto::CachedBuild::from_reference_index(
1212 fresh_build.nodes.clone(),
1213 fresh_build.path_to_abs.clone(),
1214 fresh_build.external_refs.clone(),
1215 fresh_build.id_to_path_map.clone(),
1216 0,
1217 None,
1218 );
1219
1220 assert!(
1222 !warm_build.base_function_implementation.is_empty(),
1223 "base_function_implementation should be populated on warm-loaded build"
1224 );
1225
1226 let forward = warm_build
1228 .base_function_implementation
1229 .get(&crate::types::NodeId(616))
1230 .expect("616 should be in warm-loaded base_function_implementation");
1231 assert!(
1232 forward.contains(&crate::types::NodeId(2036)),
1233 "616 → 2036 mapping should exist in warm-loaded build"
1234 );
1235
1236 let reverse = warm_build
1238 .base_function_implementation
1239 .get(&crate::types::NodeId(2036))
1240 .expect("2036 should be in warm-loaded base_function_implementation");
1241 assert!(
1242 reverse.contains(&crate::types::NodeId(616)),
1243 "2036 → 616 mapping should exist in warm-loaded build"
1244 );
1245
1246 let unlock_forward = warm_build
1248 .base_function_implementation
1249 .get(&crate::types::NodeId(183));
1250 assert!(
1251 unlock_forward.is_some(),
1252 "unlock (183) should be in warm-loaded base_function_implementation"
1253 );
1254 }
1255}