neo_solidity/ir/
ir_deploy.rs

1#[allow(clippy::too_many_arguments)]
2fn build_deploy_function(
3    metadata: &FunctionMetadata,
4    constructors: &[&Function],
5    state_variables: &[StateVariableMetadata],
6    state_index_map: &HashMap<String, usize>,
7    state_types: &[ValueType],
8    defined_struct_types: &[ValueType],
9    event_index_map: &HashMap<String, usize>,
10    event_signature_map: &HashMap<String, Vec<ManifestType>>,
11    enum_variant_map: &HashMap<String, HashMap<String, u64>>,
12    contract_types: &HashSet<String>,
13    selector_registry: &SelectorRegistry,
14    function_names: &HashSet<String>,
15    function_overloads: &HashMap<(String, usize), String>,
16    function_param_names: &HashMap<(String, usize), Vec<String>>,
17    void_functions: &HashSet<String>,
18    super_method_map: &HashMap<String, String>,
19) -> Result<Function, Vec<IrDiagnostic>> {
20    let parameters: Vec<ValueType> = metadata
21        .parameters
22        .iter()
23        .map(ValueType::from_parameter)
24        .collect();
25    let returns: Vec<ValueType> = metadata
26        .return_parameters
27        .iter()
28        .map(ValueType::from_parameter)
29        .collect();
30
31    let param_index_map = build_parameter_index_map(metadata);
32    let mut ctx = LoweringContext::new(
33        &metadata.name,
34        false,
35        param_index_map,
36        &parameters,
37        state_variables,
38        state_index_map,
39        state_types,
40        defined_struct_types,
41        event_index_map,
42        event_signature_map,
43        enum_variant_map,
44        contract_types,
45        selector_registry,
46        function_names,
47        function_overloads,
48        function_param_names,
49        void_functions,
50        super_method_map,
51    );
52
53    // Lower state variable initializers (non-constant) into a deploy-time prologue.
54    let mut init_instructions = Vec::new();
55    for (index, state) in state_variables.iter().enumerate() {
56        if state.is_constant {
57            continue;
58        }
59        if let Some(initializer) = state.initializer.as_ref() {
60            if lower_expression(initializer, &mut ctx, &mut init_instructions) {
61                init_instructions.push(Instruction::StoreState(index));
62            }
63        }
64    }
65
66    if !ctx.errors.is_empty() {
67        return Err(ctx.errors);
68    }
69
70    let mut instructions = Vec::new();
71    let mut local_count = ctx.local_count as usize;
72
73    let has_init = !init_instructions.is_empty();
74    if constructors.is_empty() && !has_init {
75        instructions.push(Instruction::ReturnVoid);
76    } else {
77        let run_label = ctx.next_label();
78        let end_label = ctx.next_label();
79
80        // `_deploy(data, update)` follows Neo N3 convention:
81        // - `update == true` means contract update; skip constructors/initializers.
82        // - `update == false` means first deployment; run initializers then constructors.
83        //
84        // Note: in this compiler's IR, `JumpIf` branches when the condition is false.
85        // Therefore, we jump into the deploy prologue when `update == false`.
86        instructions.push(Instruction::LoadParameter(1));
87        instructions.push(Instruction::JumpIf { target: run_label });
88        instructions.push(Instruction::Jump { target: end_label });
89        instructions.push(Instruction::Label(run_label));
90
91        if has_init {
92            instructions.extend(init_instructions);
93        }
94
95        let needs_data_local = constructors.iter().any(|c| !c.parameters.is_empty());
96        let data_local = local_count;
97        if needs_data_local {
98            instructions.push(Instruction::LoadParameter(0));
99            instructions.push(Instruction::StoreLocal(data_local));
100
101            // When the contract has a parameterised Solidity constructor, we treat `_deploy(data, update).data`
102            // as an array of constructor arguments.
103            //
104            // Most tooling (including Neo-Express) passes `data` as a ByteString containing JSON (e.g. `[7]`).
105            // Some SDKs can pass an Array StackItem directly. Some deploy flows (e.g., contract-to-contract
106            // deployment) may pass StdLib.serialize(...) bytes.
107            //
108            // We support all of these by attempting to parse JSON via StdLib.jsonDeserialize, then falling back
109            // to StdLib.deserialize, and finally falling back to the original `data` when native calls throw.
110            //
111            // StdLib.jsonDeserialize returns a StackItem (Array/Map/etc). Constructor arg extraction then uses
112            // PICKITEM/ArrayGet to obtain the arguments.
113            //
114            // Note: This requires `StdLib.jsonDeserialize` manifest permission.
115            let json_catch_label = ctx.next_label();
116            let deserialize_label = ctx.next_label();
117            let deserialize_catch_label = ctx.next_label();
118            let decode_done_label = ctx.next_label();
119            instructions.push(Instruction::Try {
120                catch_target: json_catch_label,
121            });
122            instructions.push(Instruction::LoadLocal(data_local));
123            instructions.push(Instruction::CallBuiltin {
124                builtin: BuiltinCall::NativeCall {
125                    contract: NativeContract::StdLib,
126                    method: "jsonDeserialize".to_string(),
127                },
128                arg_count: 1,
129            });
130            instructions.push(Instruction::StoreLocal(data_local));
131            instructions.push(Instruction::EndTry {
132                target: decode_done_label,
133            });
134
135            instructions.push(Instruction::Label(json_catch_label));
136            // NeoVM pushes the thrown value onto the stack when entering the catch handler.
137            // Drop it and attempt a binary `StdLib.deserialize` fallback.
138            instructions.push(Instruction::Drop(ValueType::Any));
139            instructions.push(Instruction::EndTry {
140                target: deserialize_label,
141            });
142
143            instructions.push(Instruction::Label(deserialize_label));
144            instructions.push(Instruction::Try {
145                catch_target: deserialize_catch_label,
146            });
147            instructions.push(Instruction::LoadLocal(data_local));
148            instructions.push(Instruction::CallBuiltin {
149                builtin: BuiltinCall::NativeCall {
150                    contract: NativeContract::StdLib,
151                    method: "deserialize".to_string(),
152                },
153                arg_count: 1,
154            });
155            instructions.push(Instruction::StoreLocal(data_local));
156            instructions.push(Instruction::EndTry {
157                target: decode_done_label,
158            });
159            instructions.push(Instruction::Label(deserialize_catch_label));
160            // Drop the thrown value and keep the original `data` unchanged.
161            instructions.push(Instruction::Drop(ValueType::Any));
162            instructions.push(Instruction::EndTry {
163                target: decode_done_label,
164            });
165
166            instructions.push(Instruction::Label(decode_done_label));
167            local_count += 1;
168        }
169
170        for constructor in constructors {
171            let param_count = constructor.parameters.len();
172            if param_count > 0 {
173                for index in 0..param_count {
174                    instructions.push(Instruction::LoadLocal(data_local));
175                    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
176                        BigInt::from(index as u64),
177                    )));
178                    instructions.push(Instruction::ArrayGet);
179                }
180            }
181
182            instructions.push(Instruction::CallFunction {
183                name: constructor.name.clone(),
184                arg_count: param_count,
185            });
186        }
187
188        instructions.push(Instruction::Label(end_label));
189        instructions.push(Instruction::ReturnVoid);
190    }
191
192    Ok(Function {
193        name: metadata.neo_name.clone(),
194        kind: FunctionKind::Regular,
195        parameters,
196        returns,
197        basic_blocks: vec![BasicBlock { instructions }],
198        local_count: local_count as u16,
199    })
200}