neo_solidity/codegen/impl/
node_gen.rs

1impl CodeGenerator {
2    #[allow(clippy::only_used_in_recursion)]
3    fn generate_node(
4        &mut self,
5        node: &AstNode,
6        bytecode: &mut Vec<u8>,
7        functions: &mut Vec<FunctionMeta>,
8        events: &mut Vec<String>,
9        estimated_gas: &mut u64,
10    ) -> Result<(), CompilerError> {
11        match &node.node_type {
12            AstNodeType::Object { statements } | AstNodeType::Block { statements } => {
13                for stmt in statements {
14                    self.generate_node(stmt, bytecode, functions, events, estimated_gas)?;
15                }
16            }
17            AstNodeType::Function {
18                name,
19                params,
20                returns,
21                body,
22            } => {
23                // Record the bytecode offset before emitting this function's entry.
24                let raw_offset = bytecode.len();
25                functions.push(FunctionMeta {
26                    name: name.clone(),
27                    params: params.clone(),
28                    returns: returns.clone(),
29                    raw_offset,
30                });
31
32                // Function entry
33                bytecode.push(0x0C); // PUSHDATA1
34                bytecode.push(name.len() as u8);
35                bytecode.extend_from_slice(name.as_bytes());
36
37                // Generate function body
38                self.generate_node(body, bytecode, functions, events, estimated_gas)?;
39
40                *estimated_gas += 512; // NeoVM CALL opcode cost
41            }
42            AstNodeType::Assignment { targets, value } => {
43                // Generate value expression first
44                self.generate_node(value, bytecode, functions, events, estimated_gas)?;
45
46                // Store to each target variable using index-based STLOC
47                for target in targets {
48                    // Get or create variable index
49                    let var_index = match self.get_variable_index(target) {
50                        Some(idx) => idx,
51                        None => self.register_variable(target),
52                    };
53
54                    // Emit STLOC with variable index
55                    emit_stloc(bytecode, var_index);
56                }
57
58                *estimated_gas += targets.len() as u64 * 8;
59            }
60            AstNodeType::FunctionCall { name, arguments } => {
61                // Generate arguments
62                for arg in arguments {
63                    self.generate_node(arg, bytecode, functions, events, estimated_gas)?;
64                }
65
66                // Generate function call based on built-in type
67                match name.as_str() {
68                    "add" => {
69                        bytecode.push(0x9E); // ADD
70                        *estimated_gas += 8; // NeoVM arithmetic cost
71                    }
72                    "sub" => {
73                        bytecode.push(0x9F); // SUB
74                        *estimated_gas += 8;
75                    }
76                    "mul" => {
77                        bytecode.push(0xA0); // MUL
78                        *estimated_gas += 8;
79                    }
80                    "div" => {
81                        bytecode.push(0xA1); // DIV
82                        *estimated_gas += 8;
83                    }
84                    "mod" => {
85                        bytecode.push(0xA2); // MOD
86                        *estimated_gas += 8;
87                    }
88                    "and" => {
89                        bytecode.push(0xA3); // AND
90                        *estimated_gas += 8;
91                    }
92                    "or" => {
93                        bytecode.push(0xA4); // OR
94                        *estimated_gas += 8;
95                    }
96                    "xor" => {
97                        bytecode.push(0xA5); // XOR
98                        *estimated_gas += 8;
99                    }
100                    "shl" => {
101                        bytecode.push(0xA8); // SHL
102                        *estimated_gas += 8;
103                    }
104                    "shr" => {
105                        bytecode.push(0xA9); // SHR
106                        *estimated_gas += 8;
107                    }
108                    "keccak256" => {
109                        // Keccak256 is a CryptoLib native method, route via System.Contract.Call
110                        bytecode.push(0x0C); // PUSHDATA1
111                        bytecode.push("keccak256".len() as u8);
112                        bytecode.extend_from_slice("keccak256".as_bytes());
113                        bytecode.push(0x41); // SYSCALL
114                        bytecode.extend_from_slice(&interop_id_bytes("System.Contract.Call"));
115                        *estimated_gas += 700000; // NeoVM contract call cost for crypto
116                    }
117                    "sha256" => {
118                        bytecode.push(0x0C); // PUSHDATA1
119                        bytecode.push("sha256".len() as u8);
120                        bytecode.extend_from_slice("sha256".as_bytes());
121                        bytecode.push(0x41); // SYSCALL
122                        bytecode.extend_from_slice(&interop_id_bytes("System.Contract.Call"));
123                        *estimated_gas += 700000;
124                    }
125                    "sload" => {
126                        bytecode.push(0x41); // SYSCALL
127                        bytecode.extend_from_slice(&interop_id_bytes("System.Storage.Get"));
128                        *estimated_gas += 100; // NeoVM StorageGet cost
129                    }
130                    "sstore" => {
131                        bytecode.push(0x41); // SYSCALL
132                        bytecode.extend_from_slice(&interop_id_bytes("System.Storage.Put"));
133                        *estimated_gas += 1000; // NeoVM StoragePut cost
134                    }
135                    _ => {
136                        // Generic function call
137                        bytecode.push(0x0C); // PUSHDATA1
138                        bytecode.push(name.len() as u8);
139                        bytecode.extend_from_slice(name.as_bytes());
140                        bytecode.push(0x41); // SYSCALL
141                        bytecode.extend_from_slice(&interop_id_bytes("System.Contract.Call"));
142                        *estimated_gas += 512; // NeoVM contract call cost
143                    }
144                }
145            }
146            AstNodeType::Literal { value } => {
147                if let Ok(num) = value.parse::<i64>() {
148                    bytecode.extend_from_slice(&crate::codegen_helpers::encode_small_int(num));
149                } else {
150                    // String or hex literal
151                    let data = if let Some(stripped) = value.strip_prefix("0x") {
152                        hex::decode(stripped).unwrap_or_else(|_| value.as_bytes().to_vec())
153                    } else {
154                        value.as_bytes().to_vec()
155                    };
156                    bytecode.push(0x0C); // PUSHDATA1
157                    bytecode.push(data.len() as u8);
158                    bytecode.extend_from_slice(&data);
159                }
160                *estimated_gas += 1; // NeoVM PUSH cost
161            }
162            AstNodeType::Identifier { name } => {
163                // Load variable by index from variable table
164                let var_index = self.get_variable_index(name).unwrap_or(0); // Default to 0 if not found
165
166                // Emit LDLOC with variable index
167                emit_ldloc(bytecode, var_index);
168
169                *estimated_gas += 1;
170            }
171            AstNodeType::If {
172                condition,
173                then_branch,
174                else_branch,
175            } => {
176                // Generate condition
177                self.generate_node(condition, bytecode, functions, events, estimated_gas)?;
178
179                // JMPIFNOT to else/end
180                bytecode.push(0x26); // JMPIFNOT
181                let else_jump_pos = bytecode.len();
182                bytecode.push(0x00); // Jump offset (patched below)
183
184                // Generate then branch
185                self.generate_node(then_branch, bytecode, functions, events, estimated_gas)?;
186
187                if else_branch.is_some() {
188                    // JMP to end
189                    bytecode.push(0x22); // JMP
190                    let end_jump_pos = bytecode.len();
191                    bytecode.push(0x00); // Jump offset (patched below)
192
193                    // Update else jump offset
194                    bytecode[else_jump_pos] = (bytecode.len() - else_jump_pos + 1) as u8;
195
196                    // Generate else branch
197                    if let Some(else_stmt) = else_branch {
198                        self.generate_node(else_stmt, bytecode, functions, events, estimated_gas)?;
199                    }
200
201                    // Update end jump offset
202                    bytecode[end_jump_pos] = (bytecode.len() - end_jump_pos + 1) as u8;
203                } else {
204                    // Update else jump offset to end
205                    bytecode[else_jump_pos] = (bytecode.len() - else_jump_pos + 1) as u8;
206                }
207
208                *estimated_gas += 8; // NeoVM JMP cost
209            }
210            AstNodeType::For {
211                init,
212                condition,
213                update,
214                body,
215            } => {
216                // Generate initialization
217                if let Some(init_node) = init {
218                    self.generate_node(init_node, bytecode, functions, events, estimated_gas)?;
219                }
220
221                let loop_start = bytecode.len();
222
223                // Push loop context for break/continue
224                let loop_end = bytecode.len() + 100; // Placeholder, will be updated
225                self.loop_stack.push(LoopContext {
226                    break_target: loop_end,
227                    continue_target: loop_start,
228                });
229
230                // Generate condition (Box<AstNode>, not Option)
231                self.generate_node(condition, bytecode, functions, events, estimated_gas)?;
232
233                // JMPIFNOT to end
234                bytecode.push(0x26); // JMPIFNOT
235                let end_jump_pos = bytecode.len();
236                bytecode.push(0x00); // Jump offset (patched below)
237
238                // Generate body (Box<AstNode>, not Option)
239                self.generate_node(body, bytecode, functions, events, estimated_gas)?;
240
241                // Generate update
242                if let Some(update_node) = update {
243                    self.generate_node(update_node, bytecode, functions, events, estimated_gas)?;
244                }
245
246                // Pop loop context
247                self.loop_stack.pop();
248
249                // Jump back to condition
250                bytecode.push(0x22); // JMP
251                bytecode.push((loop_start as i32 - bytecode.len() as i32 - 1) as u8);
252
253                // Update end jump offset
254                bytecode[end_jump_pos] = (bytecode.len() - end_jump_pos + 1) as u8;
255
256                *estimated_gas += 100; // Loop overhead
257            }
258            AstNodeType::Leave => {
259                bytecode.push(0x40); // RET
260                *estimated_gas += 1;
261            }
262            AstNodeType::Break => {
263                // Resolve break target from loop stack
264                if let Some(loop_ctx) = self.loop_stack.last() {
265                    bytecode.push(0x22); // JMP
266                    let offset = loop_ctx.break_target as i32 - bytecode.len() as i32 - 1;
267                    bytecode.push(offset as u8);
268                    *estimated_gas += 8; // NeoVM JMP cost
269                } else {
270                    return Err(CompilerError::CodegenError(
271                        "break statement outside of loop".to_string(),
272                    ));
273                }
274            }
275            AstNodeType::Continue => {
276                // Resolve continue target from loop stack
277                if let Some(loop_ctx) = self.loop_stack.last() {
278                    bytecode.push(0x22); // JMP
279                    let offset = loop_ctx.continue_target as i32 - bytecode.len() as i32 - 1;
280                    bytecode.push(offset as u8);
281                    *estimated_gas += 8; // NeoVM JMP cost
282                } else {
283                    return Err(CompilerError::CodegenError(
284                        "continue statement outside of loop".to_string(),
285                    ));
286                }
287            }
288            _ => {
289                // Handle other node types
290                *estimated_gas += 1;
291            }
292        }
293
294        Ok(())
295    }
296}
297
298/// Emit LDLOC instruction with proper index encoding
299/// NeoVM uses 0x10-0x13 for indices 0-3, or 0x14 + index for larger indices
300fn emit_ldloc(bytecode: &mut Vec<u8>, index: usize) {
301    match index {
302        0 => bytecode.push(0x10), // LDLOC0
303        1 => bytecode.push(0x11), // LDLOC1
304        2 => bytecode.push(0x12), // LDLOC2
305        3 => bytecode.push(0x13), // LDLOC3
306        _ => {
307            bytecode.push(0x14); // LDLOC with index
308            bytecode.push(index as u8);
309        }
310    }
311}
312
313/// Emit STLOC instruction with proper index encoding
314/// NeoVM uses 0x0C-0x0F for indices 0-3, or 0x14 + index for larger indices
315fn emit_stloc(bytecode: &mut Vec<u8>, index: usize) {
316    match index {
317        0 => bytecode.push(0x0C), // STLOC0
318        1 => bytecode.push(0x0D), // STLOC1
319        2 => bytecode.push(0x0E), // STLOC2
320        3 => bytecode.push(0x0F), // STLOC3
321        _ => {
322            bytecode.push(0x14); // STLOC with index
323            bytecode.push(index as u8);
324        }
325    }
326}