neo_solidity/ir/expressions/calls/
member_calls.rs1fn try_lower_member_call(
2 func: &Expression,
3 args: &[Expression],
4 ctx: &mut LoweringContext,
5 instructions: &mut Vec<Instruction>,
6) -> Option<bool> {
7 fn format_builtin_member_list(members: &[&str]) -> String {
8 const MAX_SHOWN: usize = 12;
9 if members.len() <= MAX_SHOWN {
10 return members.join(", ");
11 }
12
13 format!(
14 "{} … (+{} more)",
15 members[..MAX_SHOWN].join(", "),
16 members.len() - MAX_SHOWN
17 )
18 }
19
20 fn resolve_static_library_base(inner: &Expression, ctx: &LoweringContext) -> Option<String> {
21 match inner {
22 Expression::Variable(lib_id)
23 if !ctx.param_index_map.contains_key(&lib_id.name)
24 && ctx.resolve_local(&lib_id.name).is_none()
25 && !ctx.state_index_map.contains_key(&lib_id.name) =>
26 {
27 Some(lib_id.name.clone())
28 }
29 Expression::MemberAccess(_, namespace_expr, imported_symbol)
30 if matches!(
31 namespace_expr.as_ref(),
32 Expression::Variable(namespace_id)
33 if !ctx.param_index_map.contains_key(&namespace_id.name)
34 && ctx.resolve_local(&namespace_id.name).is_none()
35 && !ctx.state_index_map.contains_key(&namespace_id.name)
36 && !ctx.is_contract_type_name(&namespace_id.name)
37 ) && ctx.is_contract_type_name(&imported_symbol.name) =>
38 {
39 Some(imported_symbol.name.clone())
40 }
41 _ => None,
42 }
43 }
44
45 fn resolve_contract_type_name(inner: &Expression, ctx: &LoweringContext) -> Option<String> {
46 match inner {
47 Expression::Variable(type_id) if ctx.is_contract_type_name(&type_id.name) => {
48 Some(type_id.name.clone())
49 }
50 Expression::MemberAccess(_, namespace_expr, type_id)
51 if matches!(
52 namespace_expr.as_ref(),
53 Expression::Variable(namespace_id)
54 if !ctx.param_index_map.contains_key(&namespace_id.name)
55 && ctx.resolve_local(&namespace_id.name).is_none()
56 && !ctx.state_index_map.contains_key(&namespace_id.name)
57 && !ctx.is_contract_type_name(&namespace_id.name)
58 ) && ctx.is_contract_type_name(&type_id.name) =>
59 {
60 Some(type_id.name.clone())
61 }
62 _ => None,
63 }
64 }
65
66 if let Expression::MemberAccess(_, inner, member) = func {
67 if matches!(inner.as_ref(), Expression::Variable(id) if id.name == "super") {
71 if let Some(super_name) = ctx.super_method_name(&member.name) {
72 let super_name = super_name.to_string();
73 let mut success = true;
74 for arg in args {
75 if !lower_expression(arg, ctx, instructions) {
76 success = false;
77 }
78 }
79 if success {
80 let neo_name = ctx
81 .neo_function_name(&super_name, args.len())
82 .unwrap_or_else(|| super_name.clone());
83 instructions.push(Instruction::CallFunction {
84 name: neo_name,
85 arg_count: args.len(),
86 });
87 }
88 if ctx.is_void_function(&super_name) {
89 return Some(false);
90 }
91 return Some(success);
92 }
93
94 ctx.record_error_with_suggestion(
96 format!(
97 "super.{}() cannot be resolved; no overridden base method with a body was found",
98 member.name
99 ),
100 "ensure the base contract defines this function with a body and marks it 'virtual'",
101 );
102 instructions.push(Instruction::PushLiteral(
103 LiteralValue::Integer(BigInt::zero()),
104 ));
105 return Some(false);
106 }
107
108 if (member.name == "wrap" || member.name == "unwrap") && args.len() == 1 {
112 if let Expression::Variable(type_id) = inner.as_ref() {
113 let is_type_alias = !ctx.param_index_map.contains_key(&type_id.name)
114 && ctx.resolve_local(&type_id.name).is_none()
115 && !ctx.state_index_map.contains_key(&type_id.name);
116 if is_type_alias {
117 let ok = lower_expression(&args[0], ctx, instructions);
119 return Some(ok);
120 }
121 }
122 }
123
124 let is_external_target = matches!(
127 infer_type_from_expression(inner.as_ref(), ctx),
128 Some(ValueType::Address)
129 ) || matches!(
130 inner.as_ref(),
131 Expression::FunctionCall(_, cast_func, cast_args)
132 if cast_args.len() == 1
133 && resolve_contract_type_name(cast_func.as_ref(), ctx).is_some()
134 && (matches!(
135 infer_type_from_expression(&cast_args[0], ctx),
136 Some(ValueType::Address)
137 ) || address_bytes_le_from_expression(&cast_args[0]).is_some())
138 );
139
140 if is_external_target {
141 if is_low_level_evm_member(&member.name) {
142 let suggestion = match member.name.as_str() {
143 "delegatecall" => "delegatecall is not available on Neo N3; Neo contracts have isolated storage. Use Syscalls.contractCall() for cross-contract calls",
144 "staticcall" => "staticcall is not available on Neo N3; use view/pure functions or Syscalls.contractCallWithFlags() with ReadOnly flags",
145 _ => "Neo N3 does not support low-level EVM calls; use NativeCalls.sol for contract-to-contract interactions",
146 };
147 ctx.record_error_with_suggestion(
148 format!("unsupported low-level EVM call '{}'", member.name),
149 suggestion,
150 );
151 return Some(false);
152 }
153
154 let native_contract = match inner.as_ref() {
159 Expression::MemberAccess(_, base, constant)
160 if matches!(base.as_ref(), Expression::Variable(id) if id.name == "NativeCalls") =>
161 {
162 match constant.name.as_str() {
163 "NEO_CONTRACT" => Some(NativeContract::Neo),
164 "GAS_CONTRACT" => Some(NativeContract::Gas),
165 "CONTRACT_MANAGEMENT" => Some(NativeContract::ContractManagement),
166 "POLICY_CONTRACT" => Some(NativeContract::Policy),
167 "ORACLE_CONTRACT" => Some(NativeContract::Oracle),
168 "ROLE_MANAGEMENT" => Some(NativeContract::RoleManagement),
169 "NOTARY_CONTRACT" => Some(NativeContract::Notary),
170 "TREASURY_CONTRACT" => Some(NativeContract::Treasury),
171 "LEDGER_CONTRACT" => Some(NativeContract::Ledger),
172 "CRYPTO_LIB" => Some(NativeContract::CryptoLib),
173 "STD_LIB" => Some(NativeContract::StdLib),
174 _ => None,
175 }
176 }
177 _ => None,
178 };
179
180 if let Some(contract) = native_contract {
181 let mut success = true;
182 for arg in args {
183 if !lower_expression(arg, ctx, instructions) {
184 success = false;
185 }
186 }
187
188 if success {
189 instructions.push(Instruction::CallBuiltin {
190 builtin: BuiltinCall::NativeCall {
191 contract,
192 method: member.name.clone(),
193 },
194 arg_count: args.len(),
195 });
196 }
197
198 return Some(success);
199 }
200
201 let tmp_id = ctx.next_label();
208 let target_slot = ctx.allocate_local(format!("__neo_extcall_target_{tmp_id}"), None);
209
210 if !lower_expression(inner.as_ref(), ctx, instructions) {
211 return Some(false);
212 }
213 instructions.push(Instruction::StoreLocal(target_slot));
214
215 let args_slot = ctx.allocate_local(
217 format!("__neo_extcall_args_{tmp_id}"),
218 Some(ValueType::Array(Box::new(ValueType::Any))),
219 );
220
221 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
222 args.len(),
223 ))));
224 instructions.push(Instruction::NewArray {
225 element_type: ValueType::Any,
226 });
227 instructions.push(Instruction::StoreLocal(args_slot));
228
229 for (index, arg) in args.iter().enumerate() {
230 instructions.push(Instruction::LoadLocal(args_slot));
231 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
232 BigInt::from(index as u64),
233 )));
234
235 if !lower_expression(arg, ctx, instructions) {
236 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
237 BigInt::zero(),
238 )));
239 }
240
241 instructions.push(Instruction::ArraySet);
242 }
243
244 instructions.push(Instruction::LoadLocal(args_slot));
245
246 let flags = if ctx.is_safe { 0x05u8 } else { 0x0Fu8 };
249 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
250 flags,
251 ))));
252 instructions.push(Instruction::PushLiteral(LiteralValue::String(
253 member.name.as_bytes().to_vec(),
254 )));
255 instructions.push(Instruction::LoadLocal(target_slot));
256
257 instructions.push(Instruction::CallBuiltin {
258 builtin: BuiltinCall::Syscall("System.Contract.Call".to_string()),
259 arg_count: 4,
260 });
261 return Some(true);
262 }
263
264 if ctx.function_names.contains(&member.name) {
266 let static_library_base = resolve_static_library_base(inner.as_ref(), ctx);
267 let is_library_static = static_library_base.is_some();
268
269 let mut success = true;
270 if is_library_static {
271 if static_library_base.as_deref().is_some_and(|base| {
275 matches!(
276 base,
277 "Runtime" | "abi" | "Storage" | "Syscalls" | "Neo" | "NativeCalls"
278 )
279 }) {
280 let base = static_library_base.as_deref().unwrap_or("<library>");
281 let mut message =
282 format!("unsupported builtin library call '{base}.{}'", member.name);
283 if let Some(supported) = builtin_library_supported_members(base) {
284 message.push_str(&format!(
285 "; supported {base} intrinsics: {}",
286 format_builtin_member_list(supported)
287 ));
288 }
289 message.push_str(
290 ". (Builtin devpack libraries are compiler intrinsics; their Solidity bodies are not compiled.)",
291 );
292 ctx.record_error(message);
293 return Some(false);
294 }
295
296 for arg in args {
297 if !lower_expression(arg, ctx, instructions) {
298 success = false;
299 }
300 }
301
302 if success {
303 if let Some(neo_name) = ctx.neo_function_name(&member.name, args.len()) {
304 instructions.push(Instruction::CallFunction {
305 name: neo_name,
306 arg_count: args.len(),
307 });
308 } else {
309 ctx.record_error(format!(
310 "no overload of '{}' with {} argument(s)",
311 member.name,
312 args.len()
313 ));
314 success = false;
315 }
316 }
317 if ctx.is_void_function(&member.name) {
320 return Some(false);
321 }
322 return Some(success);
323 }
324
325 if !lower_expression(inner.as_ref(), ctx, instructions) {
326 success = false;
327 }
328
329 for arg in args {
330 if !lower_expression(arg, ctx, instructions) {
331 success = false;
332 }
333 }
334
335 if success {
336 let arg_count = args.len() + 1;
337 if let Some(neo_name) = ctx.neo_function_name(&member.name, arg_count) {
338 instructions.push(Instruction::CallFunction {
339 name: neo_name,
340 arg_count,
341 });
342 } else {
343 ctx.record_error(format!(
344 "no overload of '{}' with {} argument(s)",
345 member.name, arg_count
346 ));
347 success = false;
348 }
349 }
350
351 return Some(success);
352 }
353
354 if !ctx.function_names.contains(&member.name) && args.is_empty() {
358 if member.name == "next" {
359 if !lower_expression(inner.as_ref(), ctx, instructions) {
360 return Some(false);
361 }
362 instructions.push(Instruction::CallBuiltin {
363 builtin: BuiltinCall::Syscall("System.Iterator.Next".to_string()),
364 arg_count: 1,
365 });
366 return Some(true);
367 }
368
369 if member.name == "value" {
370 if !lower_expression(inner.as_ref(), ctx, instructions) {
371 return Some(false);
372 }
373 instructions.push(Instruction::CallBuiltin {
374 builtin: BuiltinCall::Syscall("System.Iterator.Value".to_string()),
375 arg_count: 1,
376 });
377 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
378 BigInt::one(),
379 )));
380 instructions.push(Instruction::ArrayGet);
381 return Some(true);
382 }
383 }
384
385 if let Some(base) = resolve_static_library_base(inner.as_ref(), ctx) {
390 if let Some(supported) = builtin_library_supported_members(base.as_str()) {
391 ctx.record_error(format!(
392 "unsupported builtin library call '{}.{}'; supported {} intrinsics: {}. (Builtin devpack libraries are compiler intrinsics; their Solidity bodies are not compiled.)",
393 base,
394 member.name,
395 base,
396 format_builtin_member_list(supported)
397 ));
398 return Some(false);
399 }
400 }
401
402 ctx.record_error(format!(
403 "unsupported external/library call '{}'",
404 member.name
405 ));
406 return Some(false);
407 }
408
409 None
410}