neo_solidity/solidity/convert/
contract.rs

1fn convert_contract(
2    contract: ContractIR,
3    inherited_events: &[EventMetadata],
4    contract_types: &[String],
5    selector_registry: std::sync::Arc<SelectorRegistry>,
6) -> ContractMetadata {
7    let structs: Vec<StructMetadata> = contract.structs.into_iter().map(convert_struct).collect();
8    let enums: Vec<EnumMetadata> = contract.enums.into_iter().map(convert_enum).collect();
9
10    let struct_type_info = structs_to_type_metadata(&structs);
11    let enum_type_info = enums_to_type_metadata(&enums);
12
13    // If the contract already defines an explicit `onNEP17Payment`, do not
14    // remap Solidity `receive()` into the Neo NEP-17 callback entrypoint.
15    //
16    // This keeps the contract compilable when authors include both `receive()`
17    // (for Solidity source compatibility) and `onNEP17Payment` (for Neo).
18    let has_explicit_on_nep17_payment = contract.functions.iter().any(|function| {
19        !matches!(function.ty, FunctionTy::Receive) && function.name == "onNEP17Payment"
20    });
21
22    let mut methods: Vec<FunctionMetadata> = contract
23        .functions
24        .into_iter()
25        .filter(|function| {
26            matches!(
27                function.ty,
28                FunctionTy::Function
29                    | FunctionTy::Constructor
30                    | FunctionTy::Fallback
31                    | FunctionTy::Receive
32            )
33        })
34        .map(|function| {
35            convert_function(
36                function,
37                &struct_type_info,
38                &enum_type_info,
39                contract_types,
40                has_explicit_on_nep17_payment,
41                &contract.type_aliases,
42            )
43        })
44        .collect();
45
46    // Mangle Neo entrypoint names for overloaded Solidity functions.
47    // Neo ABI dispatches by method name and parameter count, so overloaded
48    // functions must have unique Neo-visible names to avoid collisions during
49    // code generation and manifest export. We preserve the original Solidity
50    // name in `FunctionMetadata::name` for selector/ABI purposes.
51    use std::collections::HashMap;
52    let mut overloads: HashMap<String, Vec<usize>> = HashMap::new();
53    for (idx, method) in methods.iter().enumerate() {
54        if matches!(method.kind, FunctionKind::Regular) {
55            overloads.entry(method.name.clone()).or_default().push(idx);
56        }
57    }
58    for (name, indices) in overloads {
59        if indices.len() > 1 {
60            for index in indices {
61                let method = &mut methods[index];
62                let param_signatures: Vec<String> = method
63                    .parameters
64                    .iter()
65                    .map(|param| canonical_param_type(&param.ty))
66                    .collect();
67                method.neo_name = if param_signatures.is_empty() {
68                    format!("{name}()")
69                } else {
70                    format!("{name}({})", param_signatures.join(","))
71                };
72            }
73        }
74    }
75
76    use std::collections::BTreeMap;
77    let mut event_map: BTreeMap<String, EventMetadata> = BTreeMap::new();
78    for event in inherited_events {
79        event_map
80            .entry(event.normalized_name.clone())
81            .or_insert_with(|| event.clone());
82    }
83    for event in contract.events.into_iter().map(convert_event) {
84        event_map.insert(event.normalized_name.clone(), event);
85    }
86    let events: Vec<EventMetadata> = event_map.into_values().collect();
87    let state_variables: Vec<StateVariableMetadata> = contract
88        .state_variables
89        .into_iter()
90        .map(|var| convert_state_variable(var, &struct_type_info, &enum_type_info, contract_types, &contract.type_aliases))
91        .collect();
92
93    // Synthesize public state variable getters to match Solidity ABI behavior.
94    synthesize_public_getters(&mut methods, &state_variables);
95
96    ContractMetadata {
97        name: contract.name,
98        is_abstract: matches!(contract.kind, ContractKind::AbstractContract),
99        is_library: matches!(contract.kind, ContractKind::Library),
100        methods,
101        events,
102        uses_storage: state_variables.iter().any(|state| !state.is_constant),
103        state_variables,
104        structs,
105        enums,
106        contract_types: contract_types.to_vec(),
107        selector_registry,
108        documentation: contract.doc.into(),
109        has_using_for_star: contract.has_using_for_star,
110        has_using_function_list: contract.has_using_function_list,
111        using_for_libraries: contract.using_for_libraries.clone(),
112        has_type_definitions: contract.has_type_definitions,
113        type_aliases: contract.type_aliases,
114        flatten_warnings: Vec::new(),
115        super_method_map: contract.super_method_map,
116    }
117}
118
119fn synthesize_public_getters(
120    methods: &mut Vec<FunctionMetadata>,
121    state_variables: &[StateVariableMetadata],
122) {
123    for state in state_variables {
124        if state
125            .visibility
126            .as_deref()
127            .map(|v| v.eq_ignore_ascii_case("public"))
128            != Some(true)
129        {
130            continue;
131        }
132
133        let name = match state.name.as_deref() {
134            Some(name) => name.to_string(),
135            None => continue,
136        };
137
138        let neotype = match state.neo_type.as_ref() {
139            Some(neo) => neo.clone(),
140            None => continue,
141        };
142
143        let (parameters, return_parameters, expr) = getter_signature_from_neotype(&name, &neotype);
144        let param_signatures: Vec<String> = parameters
145            .iter()
146            .map(|param| canonical_param_type(&param.ty))
147            .collect();
148        let selector = compute_function_selector(&name, &param_signatures);
149
150        methods.push(FunctionMetadata {
151            name: name.clone(),
152            neo_name: name,
153            kind: FunctionKind::Regular,
154            parameters,
155            return_parameters,
156            state_mutability: StateMutability::View,
157            visibility: VisibilityKind::Public,
158            offset: 0,
159            body: Some(Statement::Return(Default::default(), Some(expr))),
160            selector,
161            is_virtual: false,
162            is_override: false,
163            documentation: NatspecDoc::default(),
164        });
165    }
166}
167
168fn convert_state_variable(
169    var: StateVariableIR,
170    struct_types: &[StructTypeMetadata],
171    enum_types: &[EnumTypeMetadata],
172    contract_types: &[String],
173    type_aliases: &std::collections::HashMap<String, String>,
174) -> StateVariableMetadata {
175    let ty = var.ty;
176    let neo_type = NeoType::from_solidity_with_aliases(&ty, struct_types, enum_types, contract_types, type_aliases).ok();
177    let initializer = var.initializer;
178    StateVariableMetadata {
179        name: var.name,
180        ty,
181        is_constant: var.is_constant,
182        is_immutable: var.is_immutable,
183        visibility: var.visibility,
184        neo_type,
185        has_initializer: initializer.is_some(),
186        initializer,
187    }
188}