neo_solidity/ir/expressions/calls/
value_transfer.rs

1fn try_lower_value_transfer_helpers(
2    func: &Expression,
3    args: &[Expression],
4    ctx: &mut LoweringContext,
5    instructions: &mut Vec<Instruction>,
6) -> Option<bool> {
7    // Convenience wrappers commonly used in Neo-focused devpacks:
8    //
9    // - `Neo.transferGas(from, to, amount)` -> GAS.transfer(from, to, amount, data="")
10    // - `Neo.transferNeo(from, to, amount)` -> NEO.transfer(from, to, amount, data="")
11    if let Expression::MemberAccess(_, inner, member) = func {
12        if let Expression::Variable(base) = inner.as_ref() {
13            if base.name == "Neo" && matches!(args.len(), 3 | 4) {
14                let contract = match member.name.as_str() {
15                    "transferGas" => NativeContract::Gas,
16                    "transferNeo" => NativeContract::Neo,
17                    _ => return None,
18                };
19                if !lower_expression(&args[0], ctx, instructions) {
20                    return Some(false);
21                }
22                if !lower_expression(&args[1], ctx, instructions) {
23                    return Some(false);
24                }
25                if !lower_expression(&args[2], ctx, instructions) {
26                    return Some(false);
27                }
28
29                if let Some(data) = args.get(3) {
30                    if !lower_expression(data, ctx, instructions) {
31                        return Some(false);
32                    }
33                } else {
34                    instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
35                        Vec::new(),
36                    )));
37                }
38
39                instructions.push(Instruction::CallBuiltin {
40                    builtin: BuiltinCall::NativeCall {
41                        contract,
42                        method: "transfer".to_string(),
43                    },
44                    arg_count: 4,
45                });
46
47                return Some(true);
48            }
49        }
50    }
51
52    // Solidity value-transfer helpers mapped to GAS NEP-17 transfers:
53    //
54    // - `address.transfer(amount)` aborts on failure (no return value in Solidity)
55    // - `address.send(amount)` returns bool and does not abort
56    //
57    // Neo does not have an "attached value" for contract invocations; the closest
58    // equivalent is calling the GAS native contract's `transfer` method.
59    if let Expression::MemberAccess(_, inner, member) = func {
60        if matches!(member.name.as_str(), "transfer" | "send") && args.len() == 1 {
61            let is_address_target = matches!(
62                infer_type_from_expression(inner.as_ref(), ctx),
63                Some(ValueType::Address)
64            );
65
66            if is_address_target {
67                // GAS.transfer(from, to, amount, data)
68                instructions.push(Instruction::CallBuiltin {
69                    builtin: BuiltinCall::Syscall(
70                        "System.Runtime.GetExecutingScriptHash".to_string(),
71                    ),
72                    arg_count: 0,
73                });
74
75                if !lower_expression(inner.as_ref(), ctx, instructions) {
76                    return Some(false);
77                }
78
79                if !lower_expression(&args[0], ctx, instructions) {
80                    return Some(false);
81                }
82
83                // `data` argument - empty by default.
84                instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
85                    Vec::new(),
86                )));
87
88                instructions.push(Instruction::CallBuiltin {
89                    builtin: BuiltinCall::NativeCall {
90                        contract: NativeContract::Gas,
91                        method: "transfer".to_string(),
92                    },
93                    arg_count: 4,
94                });
95
96                if member.name == "transfer" {
97                    // Abort on failure, push a truthy sentinel on success.
98                    let abort_label = ctx.next_label();
99                    let end_label = ctx.next_label();
100                    instructions.push(Instruction::JumpIf {
101                        target: abort_label,
102                    });
103                    instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
104                    instructions.push(Instruction::Jump { target: end_label });
105                    instructions.push(Instruction::Label(abort_label));
106                    instructions.push(Instruction::PushLiteral(LiteralValue::Null));
107                    instructions.push(Instruction::Throw);
108                    instructions.push(Instruction::Label(end_label));
109                }
110
111                return Some(true);
112            }
113        }
114    }
115
116    None
117}