neo_solidity/ir/expressions/calls/
low_level.rs1fn resolve_signature_string(expr: &Expression, ctx: &LoweringContext) -> Option<String> {
2 match expr {
3 Expression::Parenthesis(_, inner) => resolve_signature_string(inner, ctx),
4 Expression::StringLiteral(parts) => {
5 Some(String::from_utf8_lossy(&string_literal_bytes(parts)).to_string())
6 }
7 Expression::Variable(identifier) => {
8 let state_index = ctx.state_index_map.get(&identifier.name).copied()?;
9 let meta = ctx.state_metadata(state_index)?;
10 if !meta.is_constant {
11 return None;
12 }
13 let initializer = meta.initializer.as_ref()?;
14 resolve_signature_string(initializer, ctx)
15 }
16 Expression::FunctionCall(_, func, args) => {
17 if args.len() == 1 {
18 match func.as_ref() {
19 Expression::Type(_, _) => resolve_signature_string(&args[0], ctx),
20 Expression::Variable(id) if id.name == "bytes" || id.name == "string" => {
21 resolve_signature_string(&args[0], ctx)
22 }
23 _ => None,
24 }
25 } else {
26 None
27 }
28 }
29 _ => None,
30 }
31}
32
33fn is_single_argument_bytes_or_type_wrapper(func: &Expression, args: &[Expression]) -> bool {
34 if args.len() != 1 {
35 return false;
36 }
37
38 match func {
39 Expression::Type(_, _) => true,
40 Expression::Variable(id) => id.name == "bytes" || id.name == "string",
41 _ => false,
42 }
43}
44
45fn is_contract_type_reference(expr: &Expression, ctx: &LoweringContext) -> bool {
46 match expr {
47 Expression::Variable(type_id) => ctx.is_contract_type_name(&type_id.name),
48 Expression::MemberAccess(_, namespace_expr, type_id) => {
49 matches!(
50 namespace_expr.as_ref(),
51 Expression::Variable(namespace_id)
52 if !ctx.param_index_map.contains_key(&namespace_id.name)
53 && ctx.resolve_local(&namespace_id.name).is_none()
54 && !ctx.state_index_map.contains_key(&namespace_id.name)
55 && !ctx.is_contract_type_name(&namespace_id.name)
56 ) && ctx.is_contract_type_name(&type_id.name)
57 }
58 _ => false,
59 }
60}
61
62fn resolve_encode_call_method_name(expr: &Expression, ctx: &LoweringContext) -> Option<String> {
63 if let Some(name) = resolve_selector_method_name(expr, ctx) {
64 if !name.trim().is_empty() {
65 return Some(name);
66 }
67 }
68
69 match expr {
70 Expression::Parenthesis(_, inner) => resolve_encode_call_method_name(inner, ctx),
71 Expression::FunctionCall(_, func, args)
72 if is_single_argument_bytes_or_type_wrapper(func.as_ref(), args.as_slice()) =>
73 {
74 resolve_encode_call_method_name(&args[0], ctx)
75 }
76 Expression::MemberAccess(_, inner, member) => {
77 if member.name == "selector" {
78 if let Expression::MemberAccess(_, type_expr, function_member) = inner.as_ref() {
79 if is_contract_type_reference(type_expr.as_ref(), ctx) {
80 let function_name = function_member.name.trim();
81 if !function_name.is_empty() {
82 return Some(function_name.to_string());
83 }
84 }
85 }
86 return None;
87 }
88
89 if !is_contract_type_reference(inner.as_ref(), ctx) {
90 return None;
91 }
92
93 let name = member.name.trim();
94 if name.is_empty() {
95 None
96 } else {
97 Some(name.to_string())
98 }
99 }
100 _ => None,
101 }
102}
103
104fn extract_encode_call_arguments(expr: &Expression) -> Option<Vec<&Expression>> {
105 match expr {
106 Expression::Parenthesis(_, inner) => extract_encode_call_arguments(inner),
107 Expression::FunctionCall(_, func, args)
108 if is_single_argument_bytes_or_type_wrapper(func.as_ref(), args.as_slice()) =>
109 {
110 extract_encode_call_arguments(&args[0])
111 }
112 Expression::List(_, params) => {
113 let mut arguments = Vec::with_capacity(params.len());
114 for (_, param) in params {
115 let param = param.as_ref()?;
116 arguments.push(¶m.ty);
117 }
118 Some(arguments)
119 }
120 _ => Some(vec![expr]),
121 }
122}
123
124fn parse_low_level_call_data<'a>(
125 expr: &'a Expression,
126 ctx: &LoweringContext,
127) -> Result<Option<(String, Vec<&'a Expression>)>, String> {
128 match expr {
129 Expression::Parenthesis(_, inner) => parse_low_level_call_data(inner, ctx),
130 Expression::FunctionCall(_, func, args)
131 if is_single_argument_bytes_or_type_wrapper(func.as_ref(), args.as_slice()) =>
132 {
133 parse_low_level_call_data(&args[0], ctx)
134 }
135 Expression::FunctionCall(_, func, args) => {
136 let Expression::MemberAccess(_, inner, member) = func.as_ref() else {
137 return Ok(None);
138 };
139
140 if !matches!(inner.as_ref(), Expression::Variable(id) if id.name == "abi") {
141 return Ok(None);
142 }
143
144 match member.name.as_str() {
145 "encodeWithSignature" => {
146 let Some((first, rest)) = args.split_first() else {
147 return Err("abi.encodeWithSignature requires a signature argument".to_string());
148 };
149
150 let signature = resolve_signature_string(first, ctx).ok_or_else(|| {
151 "abi.encodeWithSignature signature must be a string literal or a constant string"
152 .to_string()
153 })?;
154
155 let name = signature
156 .split('(')
157 .next()
158 .unwrap_or(signature.as_str())
159 .trim()
160 .to_string();
161 if name.is_empty() {
162 return Err(
163 "abi.encodeWithSignature signature must include a function name".to_string(),
164 );
165 }
166 Ok(Some((name, rest.iter().collect())))
167 }
168 "encodeWithSelector" => {
169 let Some((first, rest)) = args.split_first() else {
170 return Err("abi.encodeWithSelector requires a selector argument".to_string());
171 };
172
173 let name = resolve_selector_method_name(first, ctx).ok_or_else(|| {
174 "abi.encodeWithSelector has an unsupported selector".to_string()
175 })?;
176 if name.trim().is_empty() {
177 return Err(
178 "abi.encodeWithSelector selector resolves to an empty name".to_string(),
179 );
180 }
181 Ok(Some((name, rest.iter().collect())))
182 }
183 "encodeCall" => {
184 if args.len() != 2 {
185 return Err(
186 "abi.encodeCall requires function selector and tuple argument list"
187 .to_string(),
188 );
189 }
190
191 let method_name = resolve_encode_call_method_name(&args[0], ctx)
192 .ok_or_else(|| "abi.encodeCall has an unsupported function reference".to_string())?;
193
194 let call_args = extract_encode_call_arguments(&args[1]).ok_or_else(|| {
195 "abi.encodeCall tuple argument list must contain positional expressions"
196 .to_string()
197 })?;
198
199 Ok(Some((method_name, call_args)))
200 }
201 _ => Ok(None),
202 }
203 }
204 _ => Ok(None),
205 }
206}
207
208fn resolve_call_data_local(expr: &Expression, ctx: &LoweringContext) -> Option<(usize, String)> {
209 match expr {
210 Expression::Parenthesis(_, inner) => resolve_call_data_local(inner, ctx),
211 Expression::FunctionCall(_, func, args)
212 if is_single_argument_bytes_or_type_wrapper(func.as_ref(), args.as_slice()) =>
213 {
214 resolve_call_data_local(&args[0], ctx)
215 }
216 Expression::Variable(identifier) => {
217 let slot = ctx.resolve_local(&identifier.name)?;
218 let method = ctx.call_data_method_for_local(slot)?.to_string();
219 Some((slot, method))
220 }
221 _ => None,
222 }
223}
224fn try_lower_low_level_address_call(
225 func: &Expression,
226 args: &[Expression],
227 ctx: &mut LoweringContext,
228 instructions: &mut Vec<Instruction>,
229) -> Option<bool> {
230 if let Expression::MemberAccess(_, inner, member) = func {
242 let member_name = member.name.as_str();
243 let is_staticcall = member_name == "staticcall";
244 if (member_name == "call" || is_staticcall) && args.len() == 1 {
245 if ctx.is_safe && member_name == "call" {
246 ctx.record_error(
247 "address.call(...) is not allowed in view/pure functions; use address.staticcall(...) or an external view/pure interface call",
248 );
249 return Some(false);
250 }
251
252 match parse_low_level_call_data(&args[0], ctx) {
253 Ok(Some((method_name, encode_args))) => {
254 let data_local = ctx.allocate_local("__call_data".to_string(), None);
255
256 let tuple_local = ctx.allocate_local("__call_tuple".to_string(), None);
258 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
259 2u8,
260 ))));
261 instructions.push(Instruction::NewArray {
262 element_type: ValueType::Any,
263 });
264 instructions.push(Instruction::StoreLocal(tuple_local));
265
266 if !lower_expression(inner.as_ref(), ctx, instructions) {
267 return Some(false);
268 }
269
270 instructions.push(Instruction::PushLiteral(LiteralValue::String(
271 method_name.as_bytes().to_vec(),
272 )));
273
274 let mut lowered = true;
275 for call_arg in &encode_args {
276 if !lower_expression(call_arg, ctx, instructions) {
277 lowered = false;
278 }
279 }
280
281 if !lowered {
282 return Some(false);
283 }
284
285 instructions.push(Instruction::CallBuiltin {
286 builtin: BuiltinCall::AbiEncode,
287 arg_count: encode_args.len(),
288 });
289
290 let catch_label = ctx.next_label();
291 let end_label = ctx.next_label();
292 instructions.push(Instruction::Try {
293 catch_target: catch_label,
294 });
295
296 if is_staticcall {
297 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
299 BigInt::from(0x05u8),
300 )));
301 instructions.push(Instruction::CallBuiltin {
302 builtin: BuiltinCall::ContractCallWithFlags,
303 arg_count: 4,
304 });
305 } else {
306 instructions.push(Instruction::CallBuiltin {
307 builtin: BuiltinCall::ContractCall,
308 arg_count: 3,
309 });
310 }
311
312 instructions.push(Instruction::StoreLocal(data_local));
313
314 instructions.push(Instruction::LoadLocal(tuple_local));
316 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
317 instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
318 instructions.push(Instruction::ArraySet);
319
320 instructions.push(Instruction::LoadLocal(tuple_local));
322 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
323 instructions.push(Instruction::LoadLocal(data_local));
324 instructions.push(Instruction::ArraySet);
325
326 instructions.push(Instruction::EndTry { target: end_label });
327
328 instructions.push(Instruction::Label(catch_label));
329 instructions.push(Instruction::CallBuiltin {
332 builtin: BuiltinCall::NativeCall {
333 contract: NativeContract::StdLib,
334 method: "serialize".to_string(),
335 },
336 arg_count: 1,
337 });
338 instructions.push(Instruction::StoreLocal(data_local));
339
340 instructions.push(Instruction::LoadLocal(tuple_local));
342 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
343 instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
344 instructions.push(Instruction::ArraySet);
345
346 instructions.push(Instruction::LoadLocal(tuple_local));
348 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
349 instructions.push(Instruction::LoadLocal(data_local));
350 instructions.push(Instruction::ArraySet);
351
352 instructions.push(Instruction::EndTry { target: end_label });
353 instructions.push(Instruction::Label(end_label));
354 instructions.push(Instruction::LoadLocal(tuple_local));
355 return Some(true);
356 }
357 Ok(None) => {}
358 Err(message) => {
359 ctx.record_error(message);
360 return Some(false);
361 }
362 }
363
364 if let Some((call_data_slot, method_name)) = resolve_call_data_local(&args[0], ctx) {
365 let data_local = ctx.allocate_local("__call_data".to_string(), None);
366
367 let tuple_local = ctx.allocate_local("__call_tuple".to_string(), None);
368 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
369 2u8,
370 ))));
371 instructions.push(Instruction::NewArray {
372 element_type: ValueType::Any,
373 });
374 instructions.push(Instruction::StoreLocal(tuple_local));
375
376 if !lower_expression(inner.as_ref(), ctx, instructions) {
377 return Some(false);
378 }
379
380 instructions.push(Instruction::PushLiteral(LiteralValue::String(
381 method_name.as_bytes().to_vec(),
382 )));
383 instructions.push(Instruction::LoadLocal(call_data_slot));
384
385 let catch_label = ctx.next_label();
386 let end_label = ctx.next_label();
387 instructions.push(Instruction::Try {
388 catch_target: catch_label,
389 });
390
391 if is_staticcall {
392 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::from(
393 0x05u8,
394 ))));
395 instructions.push(Instruction::CallBuiltin {
396 builtin: BuiltinCall::ContractCallWithFlags,
397 arg_count: 4,
398 });
399 } else {
400 instructions.push(Instruction::CallBuiltin {
401 builtin: BuiltinCall::ContractCall,
402 arg_count: 3,
403 });
404 }
405
406 instructions.push(Instruction::StoreLocal(data_local));
407
408 instructions.push(Instruction::LoadLocal(tuple_local));
409 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
410 instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
411 instructions.push(Instruction::ArraySet);
412
413 instructions.push(Instruction::LoadLocal(tuple_local));
414 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
415 instructions.push(Instruction::LoadLocal(data_local));
416 instructions.push(Instruction::ArraySet);
417
418 instructions.push(Instruction::EndTry { target: end_label });
419
420 instructions.push(Instruction::Label(catch_label));
421 instructions.push(Instruction::CallBuiltin {
422 builtin: BuiltinCall::NativeCall {
423 contract: NativeContract::StdLib,
424 method: "serialize".to_string(),
425 },
426 arg_count: 1,
427 });
428 instructions.push(Instruction::StoreLocal(data_local));
429
430 instructions.push(Instruction::LoadLocal(tuple_local));
431 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
432 instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
433 instructions.push(Instruction::ArraySet);
434
435 instructions.push(Instruction::LoadLocal(tuple_local));
436 instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::one())));
437 instructions.push(Instruction::LoadLocal(data_local));
438 instructions.push(Instruction::ArraySet);
439
440 instructions.push(Instruction::EndTry { target: end_label });
441 instructions.push(Instruction::Label(end_label));
442 instructions.push(Instruction::LoadLocal(tuple_local));
443 return Some(true);
444 }
445
446 ctx.record_error_with_suggestion(
447 format!("unsupported low-level EVM call '{}'", member_name),
448 "Neo N3 does not support low-level EVM calls; use NativeCalls.sol for contract-to-contract interactions",
449 );
450 return Some(false);
451 }
452 }
453
454 None
455}