neo_solidity/ir/expressions/calls/
dispatch.rs

1fn lower_function_call_expression(
2    func: &Expression,
3    args: &[Expression],
4    ctx: &mut LoweringContext,
5    instructions: &mut Vec<Instruction>,
6) -> bool {
7    let mut func = func;
8
9    fn is_compile_time_zero(expr: &Expression) -> bool {
10        match expr {
11            Expression::Parenthesis(_, inner) => is_compile_time_zero(inner),
12            Expression::FunctionCall(_, func, args) if args.len() == 1 => {
13                if matches!(
14                    func.as_ref(),
15                    Expression::Type(_, PtType::Uint(_) | PtType::Int(_))
16                ) {
17                    return is_compile_time_zero(&args[0]);
18                }
19                false
20            }
21            _ => matches!(
22                literal_from_expression(expr),
23                Some(LiteralValue::Integer(value)) if value.is_zero()
24            ),
25        }
26    }
27
28    // Solidity call options `foo{gas: ..., value: ...}()` are represented by solang-parser as a
29    // `FunctionCall` where the callee expression is a `FunctionCallBlock`. Neo N3 does not
30    // support EVM-style attached value or gas limits, but we can safely ignore `{gas: ...}` to
31    // improve compatibility with Solidity sources.
32    if let Expression::FunctionCallBlock(_, inner_call, block) = func {
33        if let Statement::Args(_, named_args) = block.as_ref() {
34            let mut unsupported = Vec::new();
35            for arg in named_args {
36                match arg.name.name.as_str() {
37                    // Neo has no per-invocation gas limit; evaluate for side effects then ignore.
38                    "gas" => {
39                        if lower_expression(&arg.expr, ctx, instructions) {
40                            instructions.push(Instruction::Drop(ValueType::Any));
41                        }
42                    }
43                    // Neo has no attached value; require explicit NEP-17 transfers instead.
44                    "value" => {
45                        if is_compile_time_zero(&arg.expr) {
46                            if lower_expression(&arg.expr, ctx, instructions) {
47                                instructions.push(Instruction::Drop(ValueType::Any));
48                            }
49                        } else {
50                            unsupported.push("value");
51                        }
52                    }
53                    "salt" => unsupported.push("salt"),
54                    other => unsupported.push(other),
55                }
56            }
57
58            if !unsupported.is_empty() {
59                ctx.record_error(format!(
60                    "function call options (`{{...}}`) are not supported ({}); Neo N3 requires explicit NEP-17 transfers (`NativeCalls.gasTransfer` / `NativeCalls.neoTransfer`) + `onNEP17Payment`",
61                    unsupported.join(", ")
62                ));
63                return false;
64            }
65
66            func = inner_call.as_ref();
67        } else {
68            ctx.record_error(
69                "function call blocks are not supported; only call options (`{...}`) are recognized",
70            );
71            return false;
72        }
73    }
74
75    if let Some(result) = try_lower_storage_array_helpers(func, args, ctx, instructions) {
76        return result;
77    }
78
79    if let Some(result) = try_lower_value_transfer_helpers(func, args, ctx, instructions) {
80        return result;
81    }
82
83    if let Some(result) = try_lower_low_level_address_call(func, args, ctx, instructions) {
84        return result;
85    }
86
87    if let Some(result) = try_lower_type_constructor_call(func, args, ctx, instructions) {
88        return result;
89    }
90
91    if let Some(result) = try_lower_struct_constructor_call(func, args, ctx, instructions) {
92        return result;
93    }
94
95    if let Some(result) = try_lower_builtin_call(func, args, ctx, instructions) {
96        return result;
97    }
98
99    if let Some(result) = try_lower_member_call(func, args, ctx, instructions) {
100        return result;
101    }
102
103    if let Some(result) = try_lower_variable_call(func, args, ctx, instructions) {
104        return result;
105    }
106
107    for arg in args {
108        load_expression(arg, ctx, instructions);
109        instructions.push(Instruction::Drop(ValueType::Any));
110    }
111    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
112        BigInt::zero(),
113    )));
114    true
115}