neo_solidity/ir/expressions/calls/
variable_calls.rs

1fn try_lower_variable_call(
2    func: &Expression,
3    args: &[Expression],
4    ctx: &mut LoweringContext,
5    instructions: &mut Vec<Instruction>,
6) -> Option<bool> {
7    if let Expression::Variable(identifier) = func {
8        if identifier.name == "require" || identifier.name == "assert" {
9            ctx.record_error(format!("{}() cannot be used as an expression", identifier.name));
10            return Some(false);
11        }
12
13        if identifier.name == "selfdestruct" {
14            // Neo N3 auto-compat: selfdestruct(addr) → ContractManagement.destroy()
15            // Note: Neo destroy does NOT transfer remaining funds to addr.
16            // The addr argument is evaluated (for side effects) then dropped.
17            eprintln!(
18                "warning: selfdestruct() auto-mapped to ContractManagement.destroy() \
19                 on Neo N3. The recipient address argument is ignored — Neo does not \
20                 transfer remaining funds on destroy. Use NativeCalls.gasTransfer() \
21                 to move funds before destroying."
22            );
23            if args.len() == 1 {
24                // Evaluate the address argument for side effects, then drop it.
25                if !lower_expression(&args[0], ctx, instructions) {
26                    return Some(false);
27                }
28                instructions.push(Instruction::Drop(ValueType::Any));
29            }
30            instructions.push(Instruction::CallBuiltin {
31                builtin: BuiltinCall::NativeCall {
32                    contract: NativeContract::ContractManagement,
33                    method: "destroy".to_string(),
34                },
35                arg_count: 0,
36            });
37            return Some(false); // void — no return value
38        }
39
40        if identifier.name == "blockhash" {
41            // Neo N3 auto-compat: blockhash(n) → Ledger.getBlockHash(n)
42            eprintln!(
43                "warning: blockhash() auto-mapped to Ledger.getBlockHash() \
44                 on Neo N3. Returns the block hash for the given index."
45            );
46            if args.len() == 1 {
47                if !lower_expression(&args[0], ctx, instructions) {
48                    return Some(false);
49                }
50            } else {
51                ctx.record_error("blockhash() requires exactly 1 argument");
52                return Some(false);
53            }
54            instructions.push(Instruction::CallBuiltin {
55                builtin: BuiltinCall::NativeCall {
56                    contract: NativeContract::Ledger,
57                    method: "getBlockHash".to_string(),
58                },
59                arg_count: 1,
60            });
61            return Some(true);
62        }
63
64        if identifier.name == "gasleft" {
65            // Neo N3 auto-compat: gasleft() → System.Runtime.GasLeft
66            instructions.push(Instruction::CallBuiltin {
67                builtin: BuiltinCall::Syscall(
68                    "System.Runtime.GasLeft".to_string(),
69                ),
70                arg_count: 0,
71            });
72            return Some(true);
73        }
74
75        // Treat `ContractType(addressExpr)` as a no-op cast for known contract/interface
76        // types when the argument is already address-like (including 20-byte hex literals).
77        if args.len() == 1 && ctx.is_contract_type_name(&identifier.name) {
78            if matches!(
79                infer_type_from_expression(&args[0], ctx),
80                Some(ValueType::Address)
81            ) {
82                return Some(lower_expression(&args[0], ctx, instructions));
83            }
84
85            if let Some(bytes) = address_bytes_le_from_expression(&args[0]) {
86                instructions.push(Instruction::PushLiteral(LiteralValue::Address(bytes)));
87                return Some(true);
88            }
89        }
90
91        if ctx.function_names.contains(&identifier.name) {
92            let mut success = true;
93            for arg in args {
94                if !lower_expression(arg, ctx, instructions) {
95                    success = false;
96                }
97            }
98
99            if success {
100                if let Some(neo_name) = ctx.neo_function_name(&identifier.name, args.len()) {
101                    instructions.push(Instruction::CallFunction {
102                        name: neo_name,
103                        arg_count: args.len(),
104                    });
105                } else {
106                    ctx.record_error(format!(
107                        "no overload of '{}' with {} argument(s)",
108                        identifier.name,
109                        args.len()
110                    ));
111                    success = false;
112                }
113            }
114
115            // Void functions don't push a value onto the stack, so return
116            // false to prevent the caller from emitting a spurious DROP.
117            if ctx.is_void_function(&identifier.name) {
118                return Some(false);
119            }
120            return Some(success);
121        }
122
123        ctx.record_error_with_suggestion(
124            format!("unsupported function call '{}'", identifier.name),
125            "check spelling or ensure the function is declared in the same contract",
126        );
127        return Some(false);
128    }
129
130    None
131}