neo_solidity/ir/expressions/calls/builtins/
member_neo.rs

1fn try_lower_neo_member_builtin(
2    base: &Identifier,
3    member: &Identifier,
4    args: &[Expression],
5    ctx: &mut LoweringContext,
6    instructions: &mut Vec<Instruction>,
7) -> Option<bool> {
8    if base.name != "Neo" {
9        return None;
10    }
11
12    match member.name.as_str() {
13        "isCommittee" => {
14            if args.len() != 1 {
15                ctx.record_error(format!(
16                    "Neo.isCommittee requires 1 argument(s), got {}",
17                    args.len()
18                ));
19                return Some(false);
20            }
21
22            // Neo.getCommittee() returns committee public keys (ECPoint[]). On Neo N3, a
23            // committee member account is the standard signature contract derived from one
24            // of these keys. Use System.Contract.CreateStandardAccount to derive the UInt160
25            // and compare against the provided address.
26            let tmp_id = ctx.next_label();
27            let account_slot = ctx.allocate_local(
28                format!("__neo_is_committee_account_{tmp_id}"),
29                Some(ValueType::Address),
30            );
31            let committee_slot = ctx.allocate_local(
32                format!("__neo_is_committee_committee_{tmp_id}"),
33                Some(ValueType::Any),
34            );
35            let index_slot = ctx.allocate_local(
36                format!("__neo_is_committee_index_{tmp_id}"),
37                Some(ValueType::Integer {
38                    signed: false,
39                    bits: 256,
40                }),
41            );
42
43            if !lower_expression(&args[0], ctx, instructions) {
44                return Some(false);
45            }
46            instructions.push(Instruction::StoreLocal(account_slot));
47
48            instructions.push(Instruction::CallBuiltin {
49                builtin: BuiltinCall::NativeCall {
50                    contract: NativeContract::Neo,
51                    method: "getCommittee".to_string(),
52                },
53                arg_count: 0,
54            });
55            instructions.push(Instruction::StoreLocal(committee_slot));
56
57            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
58                BigInt::zero(),
59            )));
60            instructions.push(Instruction::StoreLocal(index_slot));
61
62            let loop_label = ctx.next_label();
63            let advance_label = ctx.next_label();
64            let done_label = ctx.next_label();
65            let end_label = ctx.next_label();
66
67            instructions.push(Instruction::Label(loop_label));
68            instructions.push(Instruction::LoadLocal(index_slot));
69            instructions.push(Instruction::LoadLocal(committee_slot));
70            instructions.push(Instruction::GetSize);
71            instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
72            instructions.push(Instruction::JumpIf { target: done_label });
73
74            // committee[index]
75            instructions.push(Instruction::LoadLocal(committee_slot));
76            instructions.push(Instruction::LoadLocal(index_slot));
77            instructions.push(Instruction::ArrayGet);
78            instructions.push(Instruction::CallBuiltin {
79                builtin: BuiltinCall::Syscall("System.Contract.CreateStandardAccount".to_string()),
80                arg_count: 1,
81            });
82            instructions.push(Instruction::LoadLocal(account_slot));
83            instructions.push(Instruction::BinaryOp(BinaryOperator::Eq));
84            instructions.push(Instruction::JumpIf {
85                target: advance_label,
86            });
87
88            instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
89            instructions.push(Instruction::Jump { target: end_label });
90
91            instructions.push(Instruction::Label(advance_label));
92            instructions.push(Instruction::LoadLocal(index_slot));
93            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
94                BigInt::one(),
95            )));
96            instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
97            instructions.push(Instruction::StoreLocal(index_slot));
98            instructions.push(Instruction::Jump { target: loop_label });
99
100            instructions.push(Instruction::Label(done_label));
101            instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
102            instructions.push(Instruction::Label(end_label));
103            Some(true)
104        }
105        "getCommittee" => {
106            if !args.is_empty() {
107                ctx.record_error(format!(
108                    "Neo.getCommittee requires 0 argument(s), got {}",
109                    args.len()
110                ));
111                return Some(false);
112            }
113
114            // Neo native contract exposes committee members as ECPoint public keys.
115            // Convert to UInt160 standard accounts via System.Contract.CreateStandardAccount.
116            let tmp_id = ctx.next_label();
117            let committee_keys_slot = ctx.allocate_local(
118                format!("__neo_committee_keys_{tmp_id}"),
119                Some(ValueType::Any),
120            );
121            let committee_addrs_slot = ctx.allocate_local(
122                format!("__neo_committee_addrs_{tmp_id}"),
123                Some(ValueType::Array(Box::new(ValueType::Address))),
124            );
125            let index_slot = ctx.allocate_local(
126                format!("__neo_committee_index_{tmp_id}"),
127                Some(ValueType::Integer {
128                    signed: false,
129                    bits: 256,
130                }),
131            );
132
133            instructions.push(Instruction::CallBuiltin {
134                builtin: BuiltinCall::NativeCall {
135                    contract: NativeContract::Neo,
136                    method: "getCommittee".to_string(),
137                },
138                arg_count: 0,
139            });
140            instructions.push(Instruction::StoreLocal(committee_keys_slot));
141
142            instructions.push(Instruction::LoadLocal(committee_keys_slot));
143            instructions.push(Instruction::GetSize);
144            instructions.push(Instruction::NewArray {
145                element_type: ValueType::Address,
146            });
147            instructions.push(Instruction::StoreLocal(committee_addrs_slot));
148
149            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
150                BigInt::zero(),
151            )));
152            instructions.push(Instruction::StoreLocal(index_slot));
153
154            let loop_label = ctx.next_label();
155            let done_label = ctx.next_label();
156            instructions.push(Instruction::Label(loop_label));
157            instructions.push(Instruction::LoadLocal(index_slot));
158            instructions.push(Instruction::LoadLocal(committee_keys_slot));
159            instructions.push(Instruction::GetSize);
160            instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
161            instructions.push(Instruction::JumpIf { target: done_label });
162
163            // committee_addrs[index] = CreateStandardAccount(committee_keys[index])
164            instructions.push(Instruction::LoadLocal(committee_addrs_slot));
165            instructions.push(Instruction::LoadLocal(index_slot));
166            instructions.push(Instruction::LoadLocal(committee_keys_slot));
167            instructions.push(Instruction::LoadLocal(index_slot));
168            instructions.push(Instruction::ArrayGet);
169            instructions.push(Instruction::CallBuiltin {
170                builtin: BuiltinCall::Syscall("System.Contract.CreateStandardAccount".to_string()),
171                arg_count: 1,
172            });
173            instructions.push(Instruction::ArraySet);
174
175            instructions.push(Instruction::LoadLocal(index_slot));
176            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
177                BigInt::one(),
178            )));
179            instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
180            instructions.push(Instruction::StoreLocal(index_slot));
181            instructions.push(Instruction::Jump { target: loop_label });
182
183            instructions.push(Instruction::Label(done_label));
184            instructions.push(Instruction::LoadLocal(committee_addrs_slot));
185            Some(true)
186        }
187        "getValidators" => {
188            if !args.is_empty() {
189                ctx.record_error(format!(
190                    "Neo.getValidators requires 0 argument(s), got {}",
191                    args.len()
192                ));
193                return Some(false);
194            }
195
196            // Neo native contract exposes validators as ECPoint public keys.
197            // Convert to UInt160 standard accounts via System.Contract.CreateStandardAccount.
198            let tmp_id = ctx.next_label();
199            let validator_keys_slot = ctx.allocate_local(
200                format!("__neo_validator_keys_{tmp_id}"),
201                Some(ValueType::Any),
202            );
203            let validator_addrs_slot = ctx.allocate_local(
204                format!("__neo_validator_addrs_{tmp_id}"),
205                Some(ValueType::Array(Box::new(ValueType::Address))),
206            );
207            let index_slot = ctx.allocate_local(
208                format!("__neo_validator_index_{tmp_id}"),
209                Some(ValueType::Integer {
210                    signed: false,
211                    bits: 256,
212                }),
213            );
214
215            instructions.push(Instruction::CallBuiltin {
216                builtin: BuiltinCall::NativeCall {
217                    contract: NativeContract::Neo,
218                    method: "getNextBlockValidators".to_string(),
219                },
220                arg_count: 0,
221            });
222            instructions.push(Instruction::StoreLocal(validator_keys_slot));
223
224            instructions.push(Instruction::LoadLocal(validator_keys_slot));
225            instructions.push(Instruction::GetSize);
226            instructions.push(Instruction::NewArray {
227                element_type: ValueType::Address,
228            });
229            instructions.push(Instruction::StoreLocal(validator_addrs_slot));
230
231            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
232                BigInt::zero(),
233            )));
234            instructions.push(Instruction::StoreLocal(index_slot));
235
236            let loop_label = ctx.next_label();
237            let done_label = ctx.next_label();
238            instructions.push(Instruction::Label(loop_label));
239            instructions.push(Instruction::LoadLocal(index_slot));
240            instructions.push(Instruction::LoadLocal(validator_keys_slot));
241            instructions.push(Instruction::GetSize);
242            instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
243            instructions.push(Instruction::JumpIf { target: done_label });
244
245            instructions.push(Instruction::LoadLocal(validator_addrs_slot));
246            instructions.push(Instruction::LoadLocal(index_slot));
247            instructions.push(Instruction::LoadLocal(validator_keys_slot));
248            instructions.push(Instruction::LoadLocal(index_slot));
249            instructions.push(Instruction::ArrayGet);
250            instructions.push(Instruction::CallBuiltin {
251                builtin: BuiltinCall::Syscall("System.Contract.CreateStandardAccount".to_string()),
252                arg_count: 1,
253            });
254            instructions.push(Instruction::ArraySet);
255
256            instructions.push(Instruction::LoadLocal(index_slot));
257            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
258                BigInt::one(),
259            )));
260            instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
261            instructions.push(Instruction::StoreLocal(index_slot));
262            instructions.push(Instruction::Jump { target: loop_label });
263
264            instructions.push(Instruction::Label(done_label));
265            instructions.push(Instruction::LoadLocal(validator_addrs_slot));
266            Some(true)
267        }
268        "isValidator" => {
269            if args.len() != 1 {
270                ctx.record_error(format!(
271                    "Neo.isValidator requires 1 argument(s), got {}",
272                    args.len()
273                ));
274                return Some(false);
275            }
276
277            let tmp_id = ctx.next_label();
278            let account_slot = ctx.allocate_local(
279                format!("__neo_is_validator_account_{tmp_id}"),
280                Some(ValueType::Address),
281            );
282            let validators_slot = ctx.allocate_local(
283                format!("__neo_is_validator_keys_{tmp_id}"),
284                Some(ValueType::Any),
285            );
286            let index_slot = ctx.allocate_local(
287                format!("__neo_is_validator_index_{tmp_id}"),
288                Some(ValueType::Integer {
289                    signed: false,
290                    bits: 256,
291                }),
292            );
293
294            if !lower_expression(&args[0], ctx, instructions) {
295                return Some(false);
296            }
297            instructions.push(Instruction::StoreLocal(account_slot));
298
299            instructions.push(Instruction::CallBuiltin {
300                builtin: BuiltinCall::NativeCall {
301                    contract: NativeContract::Neo,
302                    method: "getNextBlockValidators".to_string(),
303                },
304                arg_count: 0,
305            });
306            instructions.push(Instruction::StoreLocal(validators_slot));
307
308            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
309                BigInt::zero(),
310            )));
311            instructions.push(Instruction::StoreLocal(index_slot));
312
313            let loop_label = ctx.next_label();
314            let advance_label = ctx.next_label();
315            let done_label = ctx.next_label();
316            let end_label = ctx.next_label();
317
318            instructions.push(Instruction::Label(loop_label));
319            instructions.push(Instruction::LoadLocal(index_slot));
320            instructions.push(Instruction::LoadLocal(validators_slot));
321            instructions.push(Instruction::GetSize);
322            instructions.push(Instruction::BinaryOp(BinaryOperator::Lt));
323            instructions.push(Instruction::JumpIf { target: done_label });
324
325            instructions.push(Instruction::LoadLocal(validators_slot));
326            instructions.push(Instruction::LoadLocal(index_slot));
327            instructions.push(Instruction::ArrayGet);
328            instructions.push(Instruction::CallBuiltin {
329                builtin: BuiltinCall::Syscall("System.Contract.CreateStandardAccount".to_string()),
330                arg_count: 1,
331            });
332            instructions.push(Instruction::LoadLocal(account_slot));
333            instructions.push(Instruction::BinaryOp(BinaryOperator::Eq));
334            instructions.push(Instruction::JumpIf {
335                target: advance_label,
336            });
337
338            instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
339            instructions.push(Instruction::Jump { target: end_label });
340
341            instructions.push(Instruction::Label(advance_label));
342            instructions.push(Instruction::LoadLocal(index_slot));
343            instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
344                BigInt::one(),
345            )));
346            instructions.push(Instruction::BinaryOp(BinaryOperator::Add));
347            instructions.push(Instruction::StoreLocal(index_slot));
348            instructions.push(Instruction::Jump { target: loop_label });
349
350            instructions.push(Instruction::Label(done_label));
351            instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
352            instructions.push(Instruction::Label(end_label));
353            Some(true)
354        }
355        _ => None,
356    }
357}