neo_solidity/solidity/
solidity_analyse.rs

1pub fn analyse_source(source: &str) -> Result<ContractMetadata, SolidityError> {
2    let mut contracts = analyse_all_sources(source)?;
3    Ok(contracts.swap_remove(0))
4}
5
6pub fn analyse_all_sources(source: &str) -> Result<Vec<ContractMetadata>, SolidityError> {
7    fn is_builtin_library_name(name: &str) -> bool {
8        matches!(
9            name,
10            "Runtime" | "abi" | "Storage" | "Syscalls" | "Neo" | "NativeCalls"
11        )
12    }
13
14    let mut primary = Vec::new();
15    let mut fallback = Vec::new();
16
17    for contract in parse_source(source)? {
18        if matches!(
19            contract.kind,
20            ContractKind::Contract | ContractKind::AbstractContract
21        ) {
22            primary.push(contract);
23        } else {
24            fallback.push(contract);
25        }
26    }
27
28    let has_primary = !primary.is_empty();
29    let libraries: Vec<ContractIR> = if has_primary {
30        fallback
31            .iter()
32            .filter(|contract| matches!(contract.kind, ContractKind::Library))
33            // Built-in helper libraries (Runtime/Storage/Syscalls/Neo) are lowered directly during
34            // IR generation. Avoid merging their Solidity bodies into user contracts since they
35            // may contain EVM-only stubs or unsupported constructs, and they would bloat bytecode.
36            .filter(|contract| !is_builtin_library_name(contract.name.as_str()))
37            .cloned()
38            .collect()
39    } else {
40        Vec::new()
41    };
42
43    // Validate user libraries before merging. Convert each library to metadata
44    // and run the standard validation pipeline to catch library-specific errors
45    // (state variables, constructors, external functions) early.
46    for lib in &libraries {
47        let lib_metadata = convert_contract(
48            lib.clone(),
49            &[],
50            &[],
51            std::sync::Arc::new(SelectorRegistry::default()),
52        );
53        let lib_diagnostics = validate_contract(&lib_metadata);
54        let lib_errors: Vec<Diagnostic> = lib_diagnostics
55            .into_iter()
56            .filter(|d| matches!(d.severity, DiagnosticSeverity::Error))
57            .collect();
58        if !lib_errors.is_empty() {
59            let messages: Vec<String> = lib_errors.iter().map(|d| {
60                let mut msg = d.message.clone();
61                if let Some(suggestion) = &d.suggestion {
62                    msg.push_str(&format!("\n  suggestion: {}", suggestion));
63                }
64                msg
65            }).collect();
66            return Err(SolidityError::analysis(messages.join("\n")));
67        }
68    }
69
70    // Merge library definitions into primary contracts so that library functions
71    // (including `using for`-style member calls) can be lowered as internal calls.
72    if has_primary && !libraries.is_empty() {
73        for contract in primary.iter_mut() {
74            for lib in &libraries {
75                contract.functions.extend(lib.functions.clone());
76                contract.state_variables.extend(lib.state_variables.clone());
77                contract.structs.extend(lib.structs.clone());
78                contract.enums.extend(lib.enums.clone());
79            }
80        }
81    }
82
83    // Build a lookup map for inheritance flattening and modifier expansion.
84    let contract_map: std::collections::HashMap<String, ContractIR> = primary
85        .iter()
86        .chain(fallback.iter())
87        .map(|contract| (contract.name.clone(), contract.clone()))
88        .collect();
89
90    // Track known contract/interface names so contract-typed variables can be
91    // represented as Neo addresses (UInt160) during type lowering.
92    let mut contract_types: Vec<String> = Vec::new();
93    let mut seen_contract_types = std::collections::HashSet::new();
94    for contract in contract_map.values() {
95        let include_as_contract_type = match contract.kind {
96            ContractKind::Contract | ContractKind::AbstractContract | ContractKind::Interface => {
97                true
98            }
99            ContractKind::Library => !is_builtin_library_name(contract.name.as_str()),
100        };
101
102        if include_as_contract_type
103            && seen_contract_types.insert(contract.name.to_ascii_lowercase())
104        {
105            contract_types.push(contract.name.clone());
106        }
107    }
108
109    // Build a shared selector registry so `.selector` expressions can resolve against
110    // any contract/interface visible to this compilation unit (including those defined
111    // after the primary contract in the same file).
112    let mut type_method_selectors: std::collections::HashMap<
113        String,
114        std::collections::HashMap<String, Vec<[u8; 4]>>,
115    > = std::collections::HashMap::new();
116    let mut interface_types: std::collections::HashSet<String> = std::collections::HashSet::new();
117    for contract in contract_map.values() {
118        if matches!(contract.kind, ContractKind::Interface) {
119            interface_types.insert(contract.name.clone());
120        }
121
122        // When building selector lookups for `.selector` / `.interfaceId`, include inherited
123        // interface methods as part of the derived interface. This matches Solidity behavior
124        // and supports patterns like `type(IChild).interfaceId` where `IChild is IParent`.
125        let selector_contract = match contract.kind {
126            ContractKind::Contract | ContractKind::AbstractContract | ContractKind::Interface => {
127                flatten_contract_inheritance(contract.clone(), &contract_map)
128                    .map(|(ir, _warnings)| ir)
129                    .unwrap_or_else(|_| contract.clone())
130            }
131            ContractKind::Library => contract.clone(),
132        };
133
134        let mut per_type: std::collections::HashMap<String, Vec<[u8; 4]>> =
135            std::collections::HashMap::new();
136
137        for function in &selector_contract.functions {
138            if !matches!(function.ty, FunctionTy::Function) {
139                continue;
140            }
141
142            if !matches!(
143                function.visibility,
144                VisibilityKind::External | VisibilityKind::Public
145            ) {
146                continue;
147            }
148
149            let param_signatures: Vec<String> = function
150                .parameters
151                .iter()
152                .map(|param| canonical_param_type(&param.ty))
153                .collect();
154            let selector = compute_function_selector(&function.name, &param_signatures);
155            per_type
156                .entry(function.name.clone())
157                .or_default()
158                .push(selector);
159        }
160
161        type_method_selectors.insert(contract.name.clone(), per_type);
162    }
163    let selector_registry = std::sync::Arc::new(SelectorRegistry {
164        type_method_selectors,
165        interface_types,
166    });
167
168    let mut selected = if has_primary { primary } else { fallback };
169
170    if selected.is_empty() {
171        return Ok(Vec::new());
172    }
173
174    let mut metadatas = Vec::new();
175    for contract in selected.drain(..) {
176        let (mut flattened, flatten_warnings) =
177            flatten_contract_inheritance(contract, &contract_map)?;
178        apply_modifiers_and_base_constructors(&mut flattened, &contract_map)?;
179        let mut metadata = convert_contract(
180            flattened,
181            &[],
182            &contract_types,
183            selector_registry.clone(),
184        );
185        metadata.flatten_warnings = flatten_warnings;
186        metadatas.push(metadata);
187    }
188
189    Ok(metadatas)
190}