1use std::collections::{HashMap, HashSet};
16
17use crate::ir::{ANFBinding, ANFMethod, ANFProgram, ANFProperty, ANFValue, ConstValue};
18
19const MAX_STACK_DEPTH: usize = 800;
24
25#[derive(Debug, Clone)]
31pub enum StackOp {
32 Push(PushValue),
33 Dup,
34 Swap,
35 Roll { depth: usize },
36 Pick { depth: usize },
37 Drop,
38 Nip,
39 Over,
40 Rot,
41 Tuck,
42 Opcode(String),
43 If {
44 then_ops: Vec<StackOp>,
45 else_ops: Vec<StackOp>,
46 },
47 Placeholder {
48 param_index: usize,
49 param_name: String,
50 },
51 PushCodeSepIndex,
52}
53
54#[derive(Debug, Clone)]
56pub enum PushValue {
57 Bool(bool),
58 Int(i128),
59 Bytes(Vec<u8>),
60}
61
62#[derive(Debug, Clone)]
64pub struct StackMethod {
65 pub name: String,
66 pub ops: Vec<StackOp>,
67 pub max_stack_depth: usize,
68 pub source_locs: Vec<Option<crate::ir::SourceLocation>>,
71}
72
73fn is_ec_builtin(name: &str) -> bool {
78 matches!(
79 name,
80 "ecAdd"
81 | "ecMul"
82 | "ecMulGen"
83 | "ecNegate"
84 | "ecOnCurve"
85 | "ecModReduce"
86 | "ecEncodeCompressed"
87 | "ecMakePoint"
88 | "ecPointX"
89 | "ecPointY"
90 )
91}
92
93fn builtin_opcodes(name: &str) -> Option<Vec<&'static str>> {
94 match name {
95 "sha256" => Some(vec!["OP_SHA256"]),
96 "ripemd160" => Some(vec!["OP_RIPEMD160"]),
97 "hash160" => Some(vec!["OP_HASH160"]),
98 "hash256" => Some(vec!["OP_HASH256"]),
99 "checkSig" => Some(vec!["OP_CHECKSIG"]),
100 "checkMultiSig" => Some(vec!["OP_CHECKMULTISIG"]),
101 "len" => Some(vec!["OP_SIZE"]),
102 "cat" => Some(vec!["OP_CAT"]),
103 "num2bin" => Some(vec!["OP_NUM2BIN"]),
104 "bin2num" => Some(vec!["OP_BIN2NUM"]),
105 "abs" => Some(vec!["OP_ABS"]),
106 "min" => Some(vec!["OP_MIN"]),
107 "max" => Some(vec!["OP_MAX"]),
108 "within" => Some(vec!["OP_WITHIN"]),
109 "split" => Some(vec!["OP_SPLIT"]),
110 "left" => Some(vec!["OP_SPLIT", "OP_DROP"]),
111 "int2str" => Some(vec!["OP_NUM2BIN"]),
112 "bool" => Some(vec!["OP_0NOTEQUAL"]),
113 "unpack" => Some(vec!["OP_BIN2NUM"]),
114 _ => None,
115 }
116}
117
118fn binop_opcodes(op: &str) -> Option<Vec<&'static str>> {
119 match op {
120 "+" => Some(vec!["OP_ADD"]),
121 "-" => Some(vec!["OP_SUB"]),
122 "*" => Some(vec!["OP_MUL"]),
123 "/" => Some(vec!["OP_DIV"]),
124 "%" => Some(vec!["OP_MOD"]),
125 "===" => Some(vec!["OP_NUMEQUAL"]),
126 "!==" => Some(vec!["OP_NUMEQUAL", "OP_NOT"]),
127 "<" => Some(vec!["OP_LESSTHAN"]),
128 ">" => Some(vec!["OP_GREATERTHAN"]),
129 "<=" => Some(vec!["OP_LESSTHANOREQUAL"]),
130 ">=" => Some(vec!["OP_GREATERTHANOREQUAL"]),
131 "&&" => Some(vec!["OP_BOOLAND"]),
132 "||" => Some(vec!["OP_BOOLOR"]),
133 "&" => Some(vec!["OP_AND"]),
134 "|" => Some(vec!["OP_OR"]),
135 "^" => Some(vec!["OP_XOR"]),
136 "<<" => Some(vec!["OP_LSHIFT"]),
137 ">>" => Some(vec!["OP_RSHIFT"]),
138 _ => None,
139 }
140}
141
142fn unaryop_opcodes(op: &str) -> Option<Vec<&'static str>> {
143 match op {
144 "!" => Some(vec!["OP_NOT"]),
145 "-" => Some(vec!["OP_NEGATE"]),
146 "~" => Some(vec!["OP_INVERT"]),
147 _ => None,
148 }
149}
150
151#[derive(Debug, Clone)]
158struct StackMap {
159 slots: Vec<String>,
160}
161
162impl StackMap {
163 fn new(initial: &[String]) -> Self {
164 StackMap {
165 slots: initial.to_vec(),
166 }
167 }
168
169 fn depth(&self) -> usize {
170 self.slots.len()
171 }
172
173 fn push(&mut self, name: &str) {
174 self.slots.push(name.to_string());
175 }
176
177 fn pop(&mut self) -> String {
178 self.slots.pop().expect("stack underflow")
179 }
180
181 fn find_depth(&self, name: &str) -> Option<usize> {
182 for (i, slot) in self.slots.iter().enumerate().rev() {
183 if slot == name {
184 return Some(self.slots.len() - 1 - i);
185 }
186 }
187 None
188 }
189
190 fn has(&self, name: &str) -> bool {
191 self.slots.iter().any(|s| s == name)
192 }
193
194 fn remove_at_depth(&mut self, depth_from_top: usize) -> String {
195 let index = self.slots.len() - 1 - depth_from_top;
196 self.slots.remove(index)
197 }
198
199 fn peek_at_depth(&self, depth_from_top: usize) -> &str {
200 let index = self.slots.len() - 1 - depth_from_top;
201 &self.slots[index]
202 }
203
204 fn rename_at_depth(&mut self, depth_from_top: usize, new_name: &str) {
205 let idx = self.slots.len() - 1 - depth_from_top;
206 self.slots[idx] = new_name.to_string();
207 }
208
209 fn swap(&mut self) {
210 let n = self.slots.len();
211 assert!(n >= 2, "stack underflow on swap");
212 self.slots.swap(n - 1, n - 2);
213 }
214
215 fn dup(&mut self) {
216 assert!(!self.slots.is_empty(), "stack underflow on dup");
217 let top = self.slots.last().unwrap().clone();
218 self.slots.push(top);
219 }
220
221 fn named_slots(&self) -> HashSet<String> {
223 self.slots.iter().filter(|s| !s.is_empty()).cloned().collect()
224 }
225}
226
227fn compute_last_uses(bindings: &[ANFBinding]) -> HashMap<String, usize> {
232 let mut last_use = HashMap::new();
233 for (i, binding) in bindings.iter().enumerate() {
234 for r in collect_refs(&binding.value) {
235 last_use.insert(r, i);
236 }
237 }
238 last_use
239}
240
241fn collect_refs(value: &ANFValue) -> Vec<String> {
242 let mut refs = Vec::new();
243 match value {
244 ANFValue::LoadParam { name } => {
245 refs.push(name.clone());
248 }
249 ANFValue::LoadProp { .. }
250 | ANFValue::GetStateScript { .. } => {}
251
252 ANFValue::LoadConst { value: v } => {
253 if let Some(s) = v.as_str() {
255 if s.len() > 5 && &s[..5] == "@ref:" {
256 refs.push(s[5..].to_string());
257 }
258 }
259 }
260
261 ANFValue::BinOp { left, right, .. } => {
262 refs.push(left.clone());
263 refs.push(right.clone());
264 }
265 ANFValue::UnaryOp { operand, .. } => {
266 refs.push(operand.clone());
267 }
268 ANFValue::Call { args, .. } => {
269 refs.extend(args.iter().cloned());
270 }
271 ANFValue::MethodCall { object, args, .. } => {
272 refs.push(object.clone());
273 refs.extend(args.iter().cloned());
274 }
275 ANFValue::If {
276 cond,
277 then,
278 else_branch,
279 } => {
280 refs.push(cond.clone());
281 for b in then {
282 refs.extend(collect_refs(&b.value));
283 }
284 for b in else_branch {
285 refs.extend(collect_refs(&b.value));
286 }
287 }
288 ANFValue::Loop { body, .. } => {
289 for b in body {
290 refs.extend(collect_refs(&b.value));
291 }
292 }
293 ANFValue::Assert { value } => {
294 refs.push(value.clone());
295 }
296 ANFValue::UpdateProp { value, .. } => {
297 refs.push(value.clone());
298 }
299 ANFValue::CheckPreimage { preimage } => {
300 refs.push(preimage.clone());
301 }
302 ANFValue::DeserializeState { preimage } => {
303 refs.push(preimage.clone());
304 }
305 ANFValue::AddOutput { satoshis, state_values, preimage } => {
306 refs.push(satoshis.clone());
307 refs.extend(state_values.iter().cloned());
308 if !preimage.is_empty() {
309 refs.push(preimage.clone());
310 }
311 }
312 ANFValue::AddRawOutput { satoshis, script_bytes } => {
313 refs.push(satoshis.clone());
314 refs.push(script_bytes.clone());
315 }
316 ANFValue::ArrayLiteral { elements } => {
317 refs.extend(elements.iter().cloned());
318 }
319 }
320 refs
321}
322
323struct LoweringContext {
328 sm: StackMap,
329 ops: Vec<StackOp>,
330 source_locs: Vec<Option<crate::ir::SourceLocation>>,
332 max_depth: usize,
333 properties: Vec<ANFProperty>,
334 private_methods: HashMap<String, ANFMethod>,
335 local_bindings: HashSet<String>,
338 outer_protected_refs: Option<HashSet<String>>,
340 inside_branch: bool,
343 current_source_loc: Option<crate::ir::SourceLocation>,
345}
346
347impl LoweringContext {
348 fn new(params: &[String], properties: &[ANFProperty]) -> Self {
349 let mut ctx = LoweringContext {
350 sm: StackMap::new(params),
351 ops: Vec::new(),
352 source_locs: Vec::new(),
353 max_depth: 0,
354 properties: properties.to_vec(),
355 private_methods: HashMap::new(),
356 local_bindings: HashSet::new(),
357 outer_protected_refs: None,
358 inside_branch: false,
359 current_source_loc: None,
360 };
361 ctx.track_depth();
362 ctx
363 }
364
365 fn track_depth(&mut self) {
366 if self.sm.depth() > self.max_depth {
367 self.max_depth = self.sm.depth();
368 }
369 }
370
371 fn emit_op(&mut self, op: StackOp) {
372 self.ops.push(op);
373 self.source_locs.push(self.current_source_loc.clone());
374 self.track_depth();
375 }
376
377 fn emit_varint_encoding(&mut self) {
387 self.emit_op(StackOp::Dup); self.sm.dup();
390 self.emit_op(StackOp::Push(PushValue::Int(253))); self.sm.push("");
392 self.emit_op(StackOp::Opcode("OP_LESSTHAN".into())); self.sm.pop();
394 self.sm.pop();
395 self.sm.push("");
396
397 self.emit_op(StackOp::Opcode("OP_IF".into()));
398 self.sm.pop(); self.emit_op(StackOp::Push(PushValue::Int(2))); self.sm.push("");
405 self.emit_op(StackOp::Opcode("OP_NUM2BIN".into())); self.sm.pop();
407 self.sm.pop();
408 self.sm.push("");
409 self.emit_op(StackOp::Push(PushValue::Int(1))); self.sm.push("");
411 self.emit_op(StackOp::Opcode("OP_SPLIT".into())); self.sm.pop();
413 self.sm.pop();
414 self.sm.push(""); self.sm.push(""); self.emit_op(StackOp::Drop); self.sm.pop();
418
419 self.emit_op(StackOp::Opcode("OP_ELSE".into()));
420
421 self.emit_op(StackOp::Push(PushValue::Int(4))); self.sm.push("");
426 self.emit_op(StackOp::Opcode("OP_NUM2BIN".into())); self.sm.pop();
428 self.sm.pop();
429 self.sm.push("");
430 self.emit_op(StackOp::Push(PushValue::Int(2))); self.sm.push("");
432 self.emit_op(StackOp::Opcode("OP_SPLIT".into())); self.sm.pop();
434 self.sm.pop();
435 self.sm.push(""); self.sm.push(""); self.emit_op(StackOp::Drop); self.sm.pop();
439 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0xfd])));
440 self.sm.push("");
441 self.emit_op(StackOp::Swap);
442 self.sm.swap();
443 self.sm.pop();
444 self.sm.pop();
445 self.emit_op(StackOp::Opcode("OP_CAT".into()));
446 self.sm.push("");
447
448 self.emit_op(StackOp::Opcode("OP_ENDIF".into()));
449 }
451
452 fn emit_push_data_encode(&mut self) {
457 self.emit_op(StackOp::Opcode("OP_SIZE".into()));
458 self.sm.push("");
459 self.emit_op(StackOp::Dup);
460 self.sm.push("");
461 self.emit_op(StackOp::Push(PushValue::Int(76)));
462 self.sm.push("");
463 self.emit_op(StackOp::Opcode("OP_LESSTHAN".into()));
464 self.sm.pop(); self.sm.pop();
465 self.sm.push("");
466
467 self.emit_op(StackOp::Opcode("OP_IF".into()));
468 self.sm.pop();
469 let sm_after_outer_if = self.sm.clone();
470
471 self.emit_op(StackOp::Push(PushValue::Int(2)));
473 self.sm.push("");
474 self.emit_op(StackOp::Opcode("OP_NUM2BIN".into()));
475 self.sm.pop(); self.sm.pop();
476 self.sm.push("");
477 self.emit_op(StackOp::Push(PushValue::Int(1)));
478 self.sm.push("");
479 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
480 self.sm.pop(); self.sm.pop();
481 self.sm.push(""); self.sm.push("");
482 self.emit_op(StackOp::Drop); self.sm.pop();
483 self.emit_op(StackOp::Swap); self.sm.swap();
484 self.sm.pop(); self.sm.pop();
485 self.emit_op(StackOp::Opcode("OP_CAT".into()));
486 self.sm.push("");
487 let sm_end_target = self.sm.clone();
488
489 self.emit_op(StackOp::Opcode("OP_ELSE".into()));
490 self.sm = sm_after_outer_if.clone();
491
492 self.emit_op(StackOp::Dup);
493 self.sm.push("");
494 self.emit_op(StackOp::Push(PushValue::Int(256)));
495 self.sm.push("");
496 self.emit_op(StackOp::Opcode("OP_LESSTHAN".into()));
497 self.sm.pop(); self.sm.pop();
498 self.sm.push("");
499
500 self.emit_op(StackOp::Opcode("OP_IF".into()));
501 self.sm.pop();
502 let sm_after_inner_if = self.sm.clone();
503
504 self.emit_op(StackOp::Push(PushValue::Int(2)));
506 self.sm.push("");
507 self.emit_op(StackOp::Opcode("OP_NUM2BIN".into()));
508 self.sm.pop(); self.sm.pop();
509 self.sm.push("");
510 self.emit_op(StackOp::Push(PushValue::Int(1)));
511 self.sm.push("");
512 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
513 self.sm.pop(); self.sm.pop();
514 self.sm.push(""); self.sm.push("");
515 self.emit_op(StackOp::Drop); self.sm.pop();
516 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x4c])));
517 self.sm.push("");
518 self.emit_op(StackOp::Swap); self.sm.swap();
519 self.sm.pop(); self.sm.pop();
520 self.emit_op(StackOp::Opcode("OP_CAT".into()));
521 self.sm.push("");
522 self.emit_op(StackOp::Swap); self.sm.swap();
523 self.sm.pop(); self.sm.pop();
524 self.emit_op(StackOp::Opcode("OP_CAT".into()));
525 self.sm.push("");
526
527 self.emit_op(StackOp::Opcode("OP_ELSE".into()));
528 self.sm = sm_after_inner_if;
529
530 self.emit_op(StackOp::Push(PushValue::Int(4)));
532 self.sm.push("");
533 self.emit_op(StackOp::Opcode("OP_NUM2BIN".into()));
534 self.sm.pop(); self.sm.pop();
535 self.sm.push("");
536 self.emit_op(StackOp::Push(PushValue::Int(2)));
537 self.sm.push("");
538 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
539 self.sm.pop(); self.sm.pop();
540 self.sm.push(""); self.sm.push("");
541 self.emit_op(StackOp::Drop); self.sm.pop();
542 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x4d])));
543 self.sm.push("");
544 self.emit_op(StackOp::Swap); self.sm.swap();
545 self.sm.pop(); self.sm.pop();
546 self.emit_op(StackOp::Opcode("OP_CAT".into()));
547 self.sm.push("");
548 self.emit_op(StackOp::Swap); self.sm.swap();
549 self.sm.pop(); self.sm.pop();
550 self.emit_op(StackOp::Opcode("OP_CAT".into()));
551 self.sm.push("");
552
553 self.emit_op(StackOp::Opcode("OP_ENDIF".into()));
554 self.emit_op(StackOp::Opcode("OP_ENDIF".into()));
555 self.sm = sm_end_target;
556 }
557
558 fn emit_push_data_decode(&mut self) {
563 self.emit_op(StackOp::Push(PushValue::Int(1)));
564 self.sm.push("");
565 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
566 self.sm.pop(); self.sm.pop();
567 self.sm.push(""); self.sm.push("");
568 self.emit_op(StackOp::Swap); self.sm.swap();
569 self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
570 self.emit_op(StackOp::Dup);
571 self.sm.push("");
572 self.emit_op(StackOp::Push(PushValue::Int(76)));
573 self.sm.push("");
574 self.emit_op(StackOp::Opcode("OP_LESSTHAN".into()));
575 self.sm.pop(); self.sm.pop();
576 self.sm.push("");
577
578 self.emit_op(StackOp::Opcode("OP_IF".into()));
579 self.sm.pop();
580 let sm_after_outer_if = self.sm.clone();
581
582 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
584 self.sm.pop(); self.sm.pop();
585 self.sm.push(""); self.sm.push("");
586 let sm_end_target = self.sm.clone();
587
588 self.emit_op(StackOp::Opcode("OP_ELSE".into()));
589 self.sm = sm_after_outer_if.clone();
590
591 self.emit_op(StackOp::Dup);
592 self.sm.push("");
593 self.emit_op(StackOp::Push(PushValue::Int(77)));
594 self.sm.push("");
595 self.emit_op(StackOp::Opcode("OP_NUMEQUAL".into()));
596 self.sm.pop(); self.sm.pop();
597 self.sm.push("");
598
599 self.emit_op(StackOp::Opcode("OP_IF".into()));
600 self.sm.pop();
601 let sm_after_inner_if = self.sm.clone();
602
603 self.emit_op(StackOp::Drop); self.sm.pop();
605 self.emit_op(StackOp::Push(PushValue::Int(2)));
606 self.sm.push("");
607 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
608 self.sm.pop(); self.sm.pop();
609 self.sm.push(""); self.sm.push("");
610 self.emit_op(StackOp::Swap); self.sm.swap();
611 self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
612 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
613 self.sm.pop(); self.sm.pop();
614 self.sm.push(""); self.sm.push("");
615
616 self.emit_op(StackOp::Opcode("OP_ELSE".into()));
617 self.sm = sm_after_inner_if;
618
619 self.emit_op(StackOp::Drop); self.sm.pop();
621 self.emit_op(StackOp::Push(PushValue::Int(1)));
622 self.sm.push("");
623 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
624 self.sm.pop(); self.sm.pop();
625 self.sm.push(""); self.sm.push("");
626 self.emit_op(StackOp::Swap); self.sm.swap();
627 self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
628 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
629 self.sm.pop(); self.sm.pop();
630 self.sm.push(""); self.sm.push("");
631
632 self.emit_op(StackOp::Opcode("OP_ENDIF".into()));
633 self.emit_op(StackOp::Opcode("OP_ENDIF".into()));
634 self.sm = sm_end_target;
635 }
636
637 fn is_last_use(&self, name: &str, current_index: usize, last_uses: &HashMap<String, usize>) -> bool {
638 match last_uses.get(name) {
639 None => true,
640 Some(&last) => last <= current_index,
641 }
642 }
643
644 fn bring_to_top(&mut self, name: &str, consume: bool) {
645 let depth = self
646 .sm
647 .find_depth(name)
648 .unwrap_or_else(|| panic!("value '{}' not found on stack", name));
649
650 if depth == 0 {
651 if !consume {
652 self.emit_op(StackOp::Dup);
653 self.sm.dup();
654 }
655 return;
656 }
657
658 if depth == 1 && consume {
659 self.emit_op(StackOp::Swap);
660 self.sm.swap();
661 return;
662 }
663
664 if consume {
665 if depth == 2 {
666 self.emit_op(StackOp::Rot);
667 let removed = self.sm.remove_at_depth(2);
668 self.sm.push(&removed);
669 } else {
670 self.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
671 self.sm.push(""); self.emit_op(StackOp::Roll { depth });
673 self.sm.pop(); let rolled = self.sm.remove_at_depth(depth);
675 self.sm.push(&rolled);
676 }
677 } else {
678 if depth == 1 {
679 self.emit_op(StackOp::Over);
680 let picked = self.sm.peek_at_depth(1).to_string();
681 self.sm.push(&picked);
682 } else {
683 self.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
684 self.sm.push(""); self.emit_op(StackOp::Pick { depth });
686 self.sm.pop(); let picked = self.sm.peek_at_depth(depth).to_string();
688 self.sm.push(&picked);
689 }
690 }
691
692 self.track_depth();
693 }
694
695 fn drain_branch_private_residue(&mut self, pre_if_names: &HashSet<String>) {
710 let mut drain_depths: Vec<usize> = Vec::new();
711 for d in 1..self.sm.depth() {
712 let name = self.sm.peek_at_depth(d);
713 if name.is_empty() {
714 drain_depths.push(d);
715 } else if !pre_if_names.contains(name) {
716 drain_depths.push(d);
717 }
718 }
719 if drain_depths.is_empty() {
720 return;
721 }
722 drain_depths.sort_by(|a, b| b.cmp(a));
723 for depth in drain_depths {
724 if depth == 1 {
725 self.emit_op(StackOp::Nip);
726 self.sm.remove_at_depth(1);
727 } else {
728 self.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
729 self.sm.push("");
730 self.emit_op(StackOp::Roll { depth });
731 self.sm.pop();
732 let rolled = self.sm.remove_at_depth(depth);
733 self.sm.push(&rolled);
734 self.emit_op(StackOp::Drop);
735 self.sm.pop();
736 }
737 }
738 }
739
740 fn lower_bindings(&mut self, bindings: &[ANFBinding], terminal_assert: bool) {
745 self.local_bindings = bindings.iter().map(|b| b.name.clone()).collect();
746 let mut last_uses = compute_last_uses(bindings);
747
748 if let Some(ref protected) = self.outer_protected_refs {
750 for r in protected {
751 last_uses.insert(r.clone(), bindings.len());
752 }
753 }
754
755 let mut last_assert_idx: isize = -1;
759 let mut terminal_if_idx: isize = -1;
760 if terminal_assert {
761 let last_binding = bindings.last();
762 if let Some(b) = last_binding {
763 if matches!(&b.value, ANFValue::If { .. }) {
764 terminal_if_idx = (bindings.len() - 1) as isize;
765 } else {
766 for i in (0..bindings.len()).rev() {
767 if matches!(&bindings[i].value, ANFValue::Assert { .. }) {
768 last_assert_idx = i as isize;
769 break;
770 }
771 }
772 }
773 }
774 }
775
776 for (i, binding) in bindings.iter().enumerate() {
777 self.current_source_loc = binding.source_loc.clone();
779
780 if matches!(&binding.value, ANFValue::Assert { .. }) && i as isize == last_assert_idx {
781 if let ANFValue::Assert { value } = &binding.value {
783 self.lower_assert(value, i, &last_uses, true);
784 }
785 } else if matches!(&binding.value, ANFValue::If { .. }) && i as isize == terminal_if_idx {
786 if let ANFValue::If { cond, then, else_branch } = &binding.value {
788 self.lower_if(&binding.name, cond, then, else_branch, i, &last_uses, true);
789 }
790 } else {
791 self.lower_binding(binding, i, &last_uses);
792 }
793 }
794 }
795
796 fn lower_binding(
797 &mut self,
798 binding: &ANFBinding,
799 binding_index: usize,
800 last_uses: &HashMap<String, usize>,
801 ) {
802 let name = &binding.name;
803 match &binding.value {
804 ANFValue::LoadParam {
805 name: param_name, ..
806 } => {
807 self.lower_load_param(name, param_name, binding_index, last_uses);
808 }
809 ANFValue::LoadProp {
810 name: prop_name, ..
811 } => {
812 self.lower_load_prop(name, prop_name);
813 }
814 ANFValue::LoadConst { .. } => {
815 self.lower_load_const(name, &binding.value, binding_index, last_uses);
816 }
817 ANFValue::BinOp {
818 op, left, right, result_type, ..
819 } => {
820 self.lower_bin_op(name, op, left, right, binding_index, last_uses, result_type.as_deref());
821 }
822 ANFValue::UnaryOp { op, operand, .. } => {
823 self.lower_unary_op(name, op, operand, binding_index, last_uses);
824 }
825 ANFValue::Call {
826 func: func_name,
827 args,
828 } => {
829 self.lower_call(name, func_name, args, binding_index, last_uses);
830 }
831 ANFValue::MethodCall {
832 object,
833 method,
834 args,
835 } => {
836 self.lower_method_call(name, object, method, args, binding_index, last_uses);
837 }
838 ANFValue::If {
839 cond,
840 then,
841 else_branch,
842 } => {
843 self.lower_if(name, cond, then, else_branch, binding_index, last_uses, false);
844 }
845 ANFValue::Loop {
846 count,
847 body,
848 iter_var,
849 } => {
850 self.lower_loop(name, *count, body, iter_var);
851 }
852 ANFValue::Assert { value } => {
853 self.lower_assert(value, binding_index, last_uses, false);
854 }
855 ANFValue::UpdateProp {
856 name: prop_name,
857 value,
858 } => {
859 self.lower_update_prop(prop_name, value, binding_index, last_uses);
860 }
861 ANFValue::GetStateScript {} => {
862 self.lower_get_state_script(name);
863 }
864 ANFValue::CheckPreimage { preimage } => {
865 self.lower_check_preimage(name, preimage, binding_index, last_uses);
866 }
867 ANFValue::DeserializeState { preimage } => {
868 self.lower_deserialize_state(preimage, binding_index, last_uses);
869 }
870 ANFValue::AddOutput { satoshis, state_values, preimage } => {
871 self.lower_add_output(name, satoshis, state_values, preimage, binding_index, last_uses);
872 }
873 ANFValue::AddRawOutput { satoshis, script_bytes } => {
874 self.lower_add_raw_output(name, satoshis, script_bytes, binding_index, last_uses);
875 }
876 ANFValue::ArrayLiteral { elements } => {
877 self.lower_array_literal(name, elements, binding_index, last_uses);
878 }
879 }
880 }
881
882 fn lower_load_param(
887 &mut self,
888 binding_name: &str,
889 param_name: &str,
890 binding_index: usize,
891 last_uses: &HashMap<String, usize>,
892 ) {
893 if self.sm.has(param_name) {
894 let is_last = self.is_last_use(param_name, binding_index, last_uses);
895 self.bring_to_top(param_name, is_last);
896 self.sm.pop();
897 self.sm.push(binding_name);
898 } else {
899 self.emit_op(StackOp::Push(PushValue::Int(0)));
900 self.sm.push(binding_name);
901 }
902 }
903
904 fn lower_load_prop(&mut self, binding_name: &str, prop_name: &str) {
905 let prop = self.properties.iter().find(|p| p.name == prop_name).cloned();
906
907 if self.sm.has(prop_name) {
908 self.bring_to_top(prop_name, false);
912 self.sm.pop();
913 } else if let Some(ref p) = prop {
914 if let Some(ref val) = p.initial_value {
915 self.push_json_value(val);
916 } else {
917 let param_index = self
920 .properties
921 .iter()
922 .position(|p2| p2.name == prop_name)
923 .unwrap_or(0);
924 self.emit_op(StackOp::Placeholder {
925 param_index,
926 param_name: prop_name.to_string(),
927 });
928 }
929 } else {
930 let param_index = self
932 .properties
933 .iter()
934 .position(|p2| p2.name == prop_name)
935 .unwrap_or(0);
936 self.emit_op(StackOp::Placeholder {
937 param_index,
938 param_name: prop_name.to_string(),
939 });
940 }
941 self.sm.push(binding_name);
942 }
943
944 fn push_json_value(&mut self, val: &serde_json::Value) {
945 match val {
946 serde_json::Value::Bool(b) => {
947 self.emit_op(StackOp::Push(PushValue::Bool(*b)));
948 }
949 serde_json::Value::Number(n) => {
950 let i = n.as_i64().map(|v| v as i128).unwrap_or(0);
951 self.emit_op(StackOp::Push(PushValue::Int(i)));
952 }
953 serde_json::Value::String(s) => {
954 let bytes = hex_to_bytes(s);
955 self.emit_op(StackOp::Push(PushValue::Bytes(bytes)));
956 }
957 _ => {
958 self.emit_op(StackOp::Push(PushValue::Int(0)));
959 }
960 }
961 }
962
963 fn lower_load_const(&mut self, binding_name: &str, value: &ANFValue, binding_index: usize, last_uses: &HashMap<String, usize>) {
964 if let Some(ConstValue::Str(ref s)) = value.const_value() {
969 if s.len() > 5 && &s[..5] == "@ref:" {
970 let ref_name = &s[5..];
971 if self.sm.has(ref_name) {
972 let consume = self.local_bindings.contains(ref_name)
977 && self.is_last_use(ref_name, binding_index, last_uses);
978 self.bring_to_top(ref_name, consume);
979 self.sm.pop();
980 self.sm.push(binding_name);
981 } else {
982 self.emit_op(StackOp::Push(PushValue::Int(0)));
984 self.sm.push(binding_name);
985 }
986 return;
987 }
988 if s == "@this" {
990 self.emit_op(StackOp::Push(PushValue::Int(0)));
991 self.sm.push(binding_name);
992 return;
993 }
994 }
995
996 match value.const_value() {
997 Some(ConstValue::Bool(b)) => {
998 self.emit_op(StackOp::Push(PushValue::Bool(b)));
999 }
1000 Some(ConstValue::Int(n)) => {
1001 self.emit_op(StackOp::Push(PushValue::Int(n)));
1002 }
1003 Some(ConstValue::Str(s)) => {
1004 let bytes = hex_to_bytes(&s);
1005 self.emit_op(StackOp::Push(PushValue::Bytes(bytes)));
1006 }
1007 None => {
1008 self.emit_op(StackOp::Push(PushValue::Int(0)));
1009 }
1010 }
1011 self.sm.push(binding_name);
1012 }
1013
1014 fn lower_bin_op(
1015 &mut self,
1016 binding_name: &str,
1017 op: &str,
1018 left: &str,
1019 right: &str,
1020 binding_index: usize,
1021 last_uses: &HashMap<String, usize>,
1022 result_type: Option<&str>,
1023 ) {
1024 let left_is_last = self.is_last_use(left, binding_index, last_uses);
1025 self.bring_to_top(left, left_is_last);
1026
1027 let right_is_last = self.is_last_use(right, binding_index, last_uses);
1028 self.bring_to_top(right, right_is_last);
1029
1030 self.sm.pop();
1031 self.sm.pop();
1032
1033 if result_type == Some("bytes") && op == "+" {
1036 self.emit_op(StackOp::Opcode("OP_CAT".to_string()));
1037 } else if result_type == Some("bytes") && (op == "===" || op == "!==") {
1038 self.emit_op(StackOp::Opcode("OP_EQUAL".to_string()));
1039 if op == "!==" {
1040 self.emit_op(StackOp::Opcode("OP_NOT".to_string()));
1041 }
1042 } else {
1043 let codes = binop_opcodes(op)
1044 .unwrap_or_else(|| panic!("unknown binary operator: {}", op));
1045 for code in codes {
1046 self.emit_op(StackOp::Opcode(code.to_string()));
1047 }
1048 }
1049
1050 self.sm.push(binding_name);
1051 self.track_depth();
1052 }
1053
1054 fn lower_unary_op(
1055 &mut self,
1056 binding_name: &str,
1057 op: &str,
1058 operand: &str,
1059 binding_index: usize,
1060 last_uses: &HashMap<String, usize>,
1061 ) {
1062 let is_last = self.is_last_use(operand, binding_index, last_uses);
1063 self.bring_to_top(operand, is_last);
1064
1065 self.sm.pop();
1066
1067 let codes = unaryop_opcodes(op)
1068 .unwrap_or_else(|| panic!("unknown unary operator: {}", op));
1069 for code in codes {
1070 self.emit_op(StackOp::Opcode(code.to_string()));
1071 }
1072
1073 self.sm.push(binding_name);
1074 self.track_depth();
1075 }
1076
1077 fn lower_call(
1078 &mut self,
1079 binding_name: &str,
1080 func_name: &str,
1081 args: &[String],
1082 binding_index: usize,
1083 last_uses: &HashMap<String, usize>,
1084 ) {
1085 if func_name == "assert" {
1087 if !args.is_empty() {
1088 let is_last = self.is_last_use(&args[0], binding_index, last_uses);
1089 self.bring_to_top(&args[0], is_last);
1090 self.sm.pop();
1091 self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
1092 self.sm.push(binding_name);
1093 }
1094 return;
1095 }
1096
1097 if func_name == "super" {
1100 self.sm.push(binding_name);
1101 return;
1102 }
1103
1104 if func_name == "checkMultiSig" && args.len() == 2 {
1106 self.lower_check_multi_sig(binding_name, args, binding_index, last_uses);
1107 return;
1108 }
1109
1110 if func_name == "__array_access" {
1111 self.lower_array_access(binding_name, args, binding_index, last_uses);
1112 return;
1113 }
1114
1115 if func_name == "reverseBytes" {
1116 self.lower_reverse_bytes(binding_name, args, binding_index, last_uses);
1117 return;
1118 }
1119
1120 if func_name == "substr" {
1121 self.lower_substr(binding_name, args, binding_index, last_uses);
1122 return;
1123 }
1124
1125 if func_name == "verifyRabinSig" {
1126 self.lower_verify_rabin_sig(binding_name, args, binding_index, last_uses);
1127 return;
1128 }
1129
1130 if func_name == "verifyWOTS" {
1131 self.lower_verify_wots(binding_name, args, binding_index, last_uses);
1132 return;
1133 }
1134
1135 if func_name.starts_with("verifySLHDSA_") {
1136 let param_key = func_name.trim_start_matches("verifySLHDSA_");
1137 self.lower_verify_slh_dsa(binding_name, param_key, args, binding_index, last_uses);
1138 return;
1139 }
1140
1141 if func_name == "sha256Compress" {
1142 self.lower_sha256_compress(binding_name, args, binding_index, last_uses);
1143 return;
1144 }
1145
1146 if func_name == "sha256Finalize" {
1147 self.lower_sha256_finalize(binding_name, args, binding_index, last_uses);
1148 return;
1149 }
1150
1151 if func_name == "blake3Compress" {
1152 self.lower_blake3_compress(binding_name, args, binding_index, last_uses);
1153 return;
1154 }
1155
1156 if func_name == "blake3Hash" {
1157 self.lower_blake3_hash(binding_name, args, binding_index, last_uses);
1158 return;
1159 }
1160
1161 if is_ec_builtin(func_name) {
1162 self.lower_ec_builtin(binding_name, func_name, args, binding_index, last_uses);
1163 return;
1164 }
1165
1166 if func_name == "safediv" {
1167 self.lower_safediv(binding_name, args, binding_index, last_uses);
1168 return;
1169 }
1170
1171 if func_name == "safemod" {
1172 self.lower_safemod(binding_name, args, binding_index, last_uses);
1173 return;
1174 }
1175
1176 if func_name == "clamp" {
1177 self.lower_clamp(binding_name, args, binding_index, last_uses);
1178 return;
1179 }
1180
1181 if func_name == "pow" {
1182 self.lower_pow(binding_name, args, binding_index, last_uses);
1183 return;
1184 }
1185
1186 if func_name == "mulDiv" {
1187 self.lower_mul_div(binding_name, args, binding_index, last_uses);
1188 return;
1189 }
1190
1191 if func_name == "percentOf" {
1192 self.lower_percent_of(binding_name, args, binding_index, last_uses);
1193 return;
1194 }
1195
1196 if func_name == "sqrt" {
1197 self.lower_sqrt(binding_name, args, binding_index, last_uses);
1198 return;
1199 }
1200
1201 if func_name == "gcd" {
1202 self.lower_gcd(binding_name, args, binding_index, last_uses);
1203 return;
1204 }
1205
1206 if func_name == "divmod" {
1207 self.lower_divmod(binding_name, args, binding_index, last_uses);
1208 return;
1209 }
1210
1211 if func_name == "log2" {
1212 self.lower_log2(binding_name, args, binding_index, last_uses);
1213 return;
1214 }
1215
1216 if func_name == "sign" {
1217 self.lower_sign(binding_name, args, binding_index, last_uses);
1218 return;
1219 }
1220
1221 if func_name == "right" {
1222 self.lower_right(binding_name, args, binding_index, last_uses);
1223 return;
1224 }
1225
1226 if func_name == "pack" || func_name == "toByteString" {
1229 if !args.is_empty() {
1230 let is_last = self.is_last_use(&args[0], binding_index, last_uses);
1231 self.bring_to_top(&args[0], is_last);
1232 self.sm.pop();
1233 }
1234 self.sm.push(binding_name);
1235 return;
1236 }
1237
1238 if func_name == "computeStateOutputHash" {
1241 self.lower_compute_state_output_hash(binding_name, args, binding_index, last_uses);
1242 return;
1243 }
1244
1245 if func_name == "computeStateOutput" {
1249 self.lower_compute_state_output(binding_name, args, binding_index, last_uses);
1250 return;
1251 }
1252
1253 if func_name == "buildChangeOutput" {
1257 self.lower_build_change_output(binding_name, args, binding_index, last_uses);
1258 return;
1259 }
1260
1261 if func_name.starts_with("extract") {
1265 self.lower_extractor(binding_name, func_name, args, binding_index, last_uses);
1266 return;
1267 }
1268
1269 for arg in args {
1271 let is_last = self.is_last_use(arg, binding_index, last_uses);
1272 self.bring_to_top(arg, is_last);
1273 }
1274
1275 for _ in args {
1276 self.sm.pop();
1277 }
1278
1279 if let Some(codes) = builtin_opcodes(func_name) {
1280 for code in codes {
1281 self.emit_op(StackOp::Opcode(code.to_string()));
1282 }
1283 } else {
1284 self.emit_op(StackOp::Push(PushValue::Int(0)));
1286 self.sm.push(binding_name);
1287 return;
1288 }
1289
1290 if func_name == "split" {
1291 self.sm.push("");
1292 self.sm.push(binding_name);
1293 } else if func_name == "len" {
1294 self.sm.push("");
1295 self.sm.push(binding_name);
1296 } else {
1297 self.sm.push(binding_name);
1298 }
1299
1300 self.track_depth();
1301 }
1302
1303 fn lower_method_call(
1304 &mut self,
1305 binding_name: &str,
1306 object: &str,
1307 method: &str,
1308 args: &[String],
1309 binding_index: usize,
1310 last_uses: &HashMap<String, usize>,
1311 ) {
1312 if self.sm.has(object) {
1315 self.bring_to_top(object, true);
1316 self.emit_op(StackOp::Drop);
1317 self.sm.pop();
1318 }
1319
1320 if method == "getStateScript" {
1321 self.lower_get_state_script(binding_name);
1322 return;
1323 }
1324
1325 if let Some(private_method) = self.private_methods.get(method).cloned() {
1327 self.inline_method_call(binding_name, &private_method, args, binding_index, last_uses);
1328 return;
1329 }
1330
1331 self.lower_call(binding_name, method, args, binding_index, last_uses);
1333 }
1334
1335 fn inline_method_call(
1338 &mut self,
1339 binding_name: &str,
1340 method: &ANFMethod,
1341 args: &[String],
1342 binding_index: usize,
1343 last_uses: &HashMap<String, usize>,
1344 ) {
1345 let mut shadowed: Vec<(String, String)> = Vec::new();
1350
1351 for (i, arg) in args.iter().enumerate() {
1353 if i < method.params.len() {
1354 let is_last = self.is_last_use(arg, binding_index, last_uses);
1355 self.bring_to_top(arg, is_last);
1356 self.sm.pop();
1357
1358 let param_name = &method.params[i].name;
1359
1360 if self.sm.has(param_name) {
1363 let existing_depth = self.sm.find_depth(param_name).unwrap();
1364 let shadowed_name = format!("__shadowed_{}_{}", binding_index, param_name);
1365 self.sm.rename_at_depth(existing_depth, &shadowed_name);
1366 shadowed.push((param_name.clone(), shadowed_name));
1367 }
1368
1369 self.sm.push(param_name);
1371 }
1372 }
1373
1374 self.lower_bindings(&method.body, false);
1376
1377 for (param_name, shadowed_name) in &shadowed {
1379 if self.sm.has(shadowed_name) {
1380 let depth = self.sm.find_depth(shadowed_name).unwrap();
1381 self.sm.rename_at_depth(depth, param_name);
1382 }
1383 }
1384
1385 if !method.body.is_empty() {
1388 let last_binding_name = &method.body[method.body.len() - 1].name;
1389 if self.sm.depth() > 0 {
1390 let top_name = self.sm.peek_at_depth(0).to_string();
1391 if top_name == *last_binding_name {
1392 self.sm.pop();
1393 self.sm.push(binding_name);
1394 }
1395 }
1396 }
1397 }
1398
1399 fn lower_if(
1400 &mut self,
1401 binding_name: &str,
1402 cond: &str,
1403 then_bindings: &[ANFBinding],
1404 else_bindings: &[ANFBinding],
1405 binding_index: usize,
1406 last_uses: &HashMap<String, usize>,
1407 terminal_assert: bool,
1408 ) {
1409 let is_last = self.is_last_use(cond, binding_index, last_uses);
1410 self.bring_to_top(cond, is_last);
1411 self.sm.pop(); let mut protected_refs = HashSet::new();
1415 for (ref_name, &last_idx) in last_uses.iter() {
1416 if last_idx > binding_index && self.sm.has(ref_name) {
1417 protected_refs.insert(ref_name.clone());
1418 }
1419 }
1420
1421 let pre_if_names = self.sm.named_slots();
1423
1424 let mut then_ctx = LoweringContext::new(&[], &self.properties);
1426 then_ctx.sm = self.sm.clone();
1427 then_ctx.outer_protected_refs = Some(protected_refs.clone());
1428 then_ctx.inside_branch = true;
1429 then_ctx.lower_bindings(then_bindings, terminal_assert);
1430
1431 then_ctx.drain_branch_private_residue(&pre_if_names);
1432
1433 if terminal_assert && then_ctx.sm.depth() > 1 {
1434 let excess = then_ctx.sm.depth() - 1;
1435 for _ in 0..excess {
1436 then_ctx.emit_op(StackOp::Nip);
1437 then_ctx.sm.remove_at_depth(1);
1438 }
1439 }
1440
1441 let mut else_ctx = LoweringContext::new(&[], &self.properties);
1443 else_ctx.sm = self.sm.clone();
1444 else_ctx.outer_protected_refs = Some(protected_refs);
1445 else_ctx.inside_branch = true;
1446 else_ctx.lower_bindings(else_bindings, terminal_assert);
1447
1448 else_ctx.drain_branch_private_residue(&pre_if_names);
1449
1450 if terminal_assert && else_ctx.sm.depth() > 1 {
1451 let excess = else_ctx.sm.depth() - 1;
1452 for _ in 0..excess {
1453 else_ctx.emit_op(StackOp::Nip);
1454 else_ctx.sm.remove_at_depth(1);
1455 }
1456 }
1457
1458 let post_then_names = then_ctx.sm.named_slots();
1471 let mut consumed_names: Vec<String> = Vec::new();
1472 for name in &pre_if_names {
1473 if !post_then_names.contains(name) && else_ctx.sm.has(name) {
1474 consumed_names.push(name.clone());
1475 }
1476 }
1477 let post_else_names = else_ctx.sm.named_slots();
1478 let mut else_consumed_names: Vec<String> = Vec::new();
1479 for name in &pre_if_names {
1480 if !post_else_names.contains(name) && then_ctx.sm.has(name) {
1481 else_consumed_names.push(name.clone());
1482 }
1483 }
1484
1485 if !consumed_names.is_empty() {
1488 let mut depths: Vec<usize> = consumed_names
1489 .iter()
1490 .map(|n| else_ctx.sm.find_depth(n).unwrap())
1491 .collect();
1492 depths.sort_by(|a, b| b.cmp(a));
1493 for depth in depths {
1494 if depth == 0 {
1495 else_ctx.emit_op(StackOp::Drop);
1496 else_ctx.sm.pop();
1497 } else if depth == 1 {
1498 else_ctx.emit_op(StackOp::Nip);
1499 else_ctx.sm.remove_at_depth(1);
1500 } else {
1501 else_ctx.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
1502 else_ctx.sm.push("");
1503 else_ctx.emit_op(StackOp::Roll { depth });
1504 else_ctx.sm.pop();
1505 let rolled = else_ctx.sm.remove_at_depth(depth);
1506 else_ctx.sm.push(&rolled);
1507 else_ctx.emit_op(StackOp::Drop);
1508 else_ctx.sm.pop();
1509 }
1510 }
1511 }
1512 if !else_consumed_names.is_empty() {
1513 let mut depths: Vec<usize> = else_consumed_names
1514 .iter()
1515 .map(|n| then_ctx.sm.find_depth(n).unwrap())
1516 .collect();
1517 depths.sort_by(|a, b| b.cmp(a));
1518 for depth in depths {
1519 if depth == 0 {
1520 then_ctx.emit_op(StackOp::Drop);
1521 then_ctx.sm.pop();
1522 } else if depth == 1 {
1523 then_ctx.emit_op(StackOp::Nip);
1524 then_ctx.sm.remove_at_depth(1);
1525 } else {
1526 then_ctx.emit_op(StackOp::Push(PushValue::Int(depth as i128)));
1527 then_ctx.sm.push("");
1528 then_ctx.emit_op(StackOp::Roll { depth });
1529 then_ctx.sm.pop();
1530 let rolled = then_ctx.sm.remove_at_depth(depth);
1531 then_ctx.sm.push(&rolled);
1532 then_ctx.emit_op(StackOp::Drop);
1533 then_ctx.sm.pop();
1534 }
1535 }
1536 }
1537
1538 if then_ctx.sm.depth() > else_ctx.sm.depth() {
1541 let then_top_p3 = then_ctx.sm.peek_at_depth(0).to_string();
1545 if else_bindings.is_empty() && !then_top_p3.is_empty() && else_ctx.sm.has(&then_top_p3) {
1546 let var_depth = else_ctx.sm.find_depth(&then_top_p3).unwrap();
1547 if var_depth == 0 {
1548 else_ctx.emit_op(StackOp::Dup);
1549 } else {
1550 else_ctx.emit_op(StackOp::Push(PushValue::Int(var_depth as i128)));
1551 else_ctx.sm.push("");
1552 else_ctx.emit_op(StackOp::Pick { depth: var_depth });
1553 else_ctx.sm.pop();
1554 }
1555 else_ctx.sm.push(&then_top_p3);
1556 } else {
1557 else_ctx.emit_op(StackOp::Push(PushValue::Bytes(Vec::new())));
1558 else_ctx.sm.push("");
1559 }
1560 } else if else_ctx.sm.depth() > then_ctx.sm.depth() {
1561 then_ctx.emit_op(StackOp::Push(PushValue::Bytes(Vec::new())));
1562 then_ctx.sm.push("");
1563 }
1564
1565 let then_ops = then_ctx.ops;
1566 let else_ops = else_ctx.ops;
1567
1568 self.emit_op(StackOp::If {
1569 then_ops,
1570 else_ops: if else_ops.is_empty() {
1571 Vec::new()
1572 } else {
1573 else_ops
1574 },
1575 });
1576
1577 let post_branch_names = then_ctx.sm.named_slots();
1579 for name in &pre_if_names {
1580 if !post_branch_names.contains(name) && self.sm.has(name) {
1581 if let Some(depth) = self.sm.find_depth(name) {
1582 self.sm.remove_at_depth(depth);
1583 }
1584 }
1585 }
1586
1587 if then_ctx.sm.depth() > self.sm.depth() {
1589 let then_top = then_ctx.sm.peek_at_depth(0).to_string();
1590 let else_top = if else_ctx.sm.depth() > 0 {
1591 else_ctx.sm.peek_at_depth(0).to_string()
1592 } else {
1593 String::new()
1594 };
1595 let is_property = self.properties.iter().any(|p| p.name == then_top);
1596 if is_property && !then_top.is_empty() && then_top == else_top
1597 && then_top != binding_name && self.sm.has(&then_top)
1598 {
1599 self.sm.push(&then_top);
1601 for d in 1..self.sm.depth() {
1602 if self.sm.peek_at_depth(d) == then_top {
1603 if d == 1 {
1604 self.emit_op(StackOp::Nip);
1605 self.sm.remove_at_depth(1);
1606 } else {
1607 self.emit_op(StackOp::Push(PushValue::Int(d as i128)));
1608 self.sm.push("");
1609 self.emit_op(StackOp::Roll { depth: d + 1 });
1610 self.sm.pop();
1611 let rolled = self.sm.remove_at_depth(d);
1612 self.sm.push(&rolled);
1613 self.emit_op(StackOp::Drop);
1614 self.sm.pop();
1615 }
1616 break;
1617 }
1618 }
1619 } else if !then_top.is_empty() && !is_property && else_bindings.is_empty()
1620 && then_top != binding_name && self.sm.has(&then_top)
1621 {
1622 self.sm.push(&then_top);
1626 for d in 1..self.sm.depth() {
1627 if self.sm.peek_at_depth(d) == then_top {
1628 if d == 1 {
1629 self.emit_op(StackOp::Nip);
1630 self.sm.remove_at_depth(1);
1631 } else {
1632 self.emit_op(StackOp::Push(PushValue::Int(d as i128)));
1633 self.sm.push("");
1634 self.emit_op(StackOp::Roll { depth: d + 1 });
1635 self.sm.pop();
1636 let rolled = self.sm.remove_at_depth(d);
1637 self.sm.push(&rolled);
1638 self.emit_op(StackOp::Drop);
1639 self.sm.pop();
1640 }
1641 break;
1642 }
1643 }
1644 } else {
1645 self.sm.push(binding_name);
1646 }
1647 } else if else_ctx.sm.depth() > self.sm.depth() {
1648 self.sm.push(binding_name);
1649 } else {
1650 }
1652 self.track_depth();
1653
1654 if then_ctx.max_depth > self.max_depth {
1655 self.max_depth = then_ctx.max_depth;
1656 }
1657 if else_ctx.max_depth > self.max_depth {
1658 self.max_depth = else_ctx.max_depth;
1659 }
1660 }
1661
1662 fn lower_loop(
1663 &mut self,
1664 _binding_name: &str,
1665 count: usize,
1666 body: &[ANFBinding],
1667 iter_var: &str,
1668 ) {
1669 let body_binding_names: HashSet<String> = body.iter().map(|b| b.name.clone()).collect();
1672 let mut outer_refs = HashSet::new();
1673 for b in body {
1674 if let ANFValue::LoadParam { name } = &b.value {
1675 if name != iter_var {
1676 outer_refs.insert(name.clone());
1677 }
1678 }
1679 if let ANFValue::LoadConst { value: v } = &b.value {
1681 if let Some(s) = v.as_str() {
1682 if s.len() > 5 && &s[..5] == "@ref:" {
1683 let ref_name = &s[5..];
1684 if !body_binding_names.contains(ref_name) {
1685 outer_refs.insert(ref_name.to_string());
1686 }
1687 }
1688 }
1689 }
1690 }
1691
1692 let prev_local_bindings = self.local_bindings.clone();
1695 self.local_bindings = self.local_bindings.union(&body_binding_names).cloned().collect();
1696
1697 for i in 0..count {
1698 self.emit_op(StackOp::Push(PushValue::Int(i as i128)));
1699 self.sm.push(iter_var);
1700
1701 let mut last_uses = compute_last_uses(body);
1702
1703 if i < count - 1 {
1706 for ref_name in &outer_refs {
1707 last_uses.insert(ref_name.clone(), body.len());
1708 }
1709 }
1710
1711 for (j, binding) in body.iter().enumerate() {
1712 self.lower_binding(binding, j, &last_uses);
1713 }
1714
1715 if self.sm.has(iter_var) {
1718 let depth = self.sm.find_depth(iter_var);
1719 if let Some(0) = depth {
1720 self.emit_op(StackOp::Drop);
1721 self.sm.pop();
1722 }
1723 }
1724 }
1725 self.local_bindings = prev_local_bindings;
1727 }
1731
1732 fn lower_assert(
1733 &mut self,
1734 value_ref: &str,
1735 binding_index: usize,
1736 last_uses: &HashMap<String, usize>,
1737 terminal: bool,
1738 ) {
1739 let is_last = self.is_last_use(value_ref, binding_index, last_uses);
1740 self.bring_to_top(value_ref, is_last);
1741 if terminal {
1742 } else {
1745 self.sm.pop();
1746 self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
1747 }
1748 self.track_depth();
1749 }
1750
1751 fn lower_update_prop(
1752 &mut self,
1753 prop_name: &str,
1754 value_ref: &str,
1755 binding_index: usize,
1756 last_uses: &HashMap<String, usize>,
1757 ) {
1758 let is_last = self.is_last_use(value_ref, binding_index, last_uses);
1759 self.bring_to_top(value_ref, is_last);
1760 self.sm.pop();
1761 self.sm.push(prop_name);
1762
1763 if !self.inside_branch {
1770 for d in 1..self.sm.depth() {
1771 if self.sm.peek_at_depth(d) == prop_name {
1772 if d == 1 {
1773 self.emit_op(StackOp::Nip);
1774 self.sm.remove_at_depth(1);
1775 } else {
1776 self.emit_op(StackOp::Push(PushValue::Int(d as i128)));
1777 self.sm.push("");
1778 self.emit_op(StackOp::Roll { depth: d + 1 });
1779 self.sm.pop();
1780 let rolled = self.sm.remove_at_depth(d);
1781 self.sm.push(&rolled);
1782 self.emit_op(StackOp::Drop);
1783 self.sm.pop();
1784 }
1785 break;
1786 }
1787 }
1788 }
1789
1790 self.track_depth();
1791 }
1792
1793 fn lower_get_state_script(&mut self, binding_name: &str) {
1794 let state_props: Vec<ANFProperty> = self
1795 .properties
1796 .iter()
1797 .filter(|p| !p.readonly)
1798 .cloned()
1799 .collect();
1800
1801 if state_props.is_empty() {
1802 self.emit_op(StackOp::Push(PushValue::Bytes(Vec::new())));
1803 self.sm.push(binding_name);
1804 return;
1805 }
1806
1807 let mut first = true;
1808 for prop in &state_props {
1809 if self.sm.has(&prop.name) {
1810 self.bring_to_top(&prop.name, true); } else if let Some(ref val) = prop.initial_value {
1812 self.push_json_value(val);
1813 self.sm.push("");
1814 } else {
1815 self.emit_op(StackOp::Push(PushValue::Int(0)));
1816 self.sm.push("");
1817 }
1818
1819 if prop.prop_type == "bigint" {
1821 self.emit_op(StackOp::Push(PushValue::Int(8)));
1822 self.sm.push("");
1823 self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
1824 self.sm.pop(); } else if prop.prop_type == "boolean" {
1826 self.emit_op(StackOp::Push(PushValue::Int(1)));
1827 self.sm.push("");
1828 self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
1829 self.sm.pop(); } else if prop.prop_type == "ByteString" {
1831 self.emit_push_data_encode();
1833 }
1834 if !first {
1837 self.sm.pop();
1838 self.sm.pop();
1839 self.emit_op(StackOp::Opcode("OP_CAT".to_string()));
1840 self.sm.push("");
1841 }
1842 first = false;
1843 }
1844
1845 self.sm.pop();
1846 self.sm.push(binding_name);
1847 self.track_depth();
1848 }
1849
1850 fn lower_compute_state_output_hash(
1854 &mut self,
1855 binding_name: &str,
1856 args: &[String],
1857 binding_index: usize,
1858 last_uses: &std::collections::HashMap<String, usize>,
1859 ) {
1860 let preimage_ref = &args[0];
1861 let state_bytes_ref = &args[1];
1862
1863 let sb_last = self.is_last_use(state_bytes_ref, binding_index, last_uses);
1865 self.bring_to_top(state_bytes_ref, sb_last);
1866
1867 let pre_last = self.is_last_use(preimage_ref, binding_index, last_uses);
1869 self.bring_to_top(preimage_ref, pre_last);
1870
1871 self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1873 self.sm.push("");
1874 self.emit_op(StackOp::Push(PushValue::Int(52))); self.sm.push("");
1876 self.emit_op(StackOp::Opcode("OP_SUB".into()));
1877 self.sm.pop();
1878 self.sm.pop();
1879 self.sm.push("");
1880 self.emit_op(StackOp::Opcode("OP_SPLIT".into())); self.sm.pop();
1882 self.sm.pop();
1883 self.sm.push(""); self.sm.push(""); self.emit_op(StackOp::Nip); self.sm.pop();
1887 self.sm.pop();
1888 self.sm.push("");
1889 self.emit_op(StackOp::Push(PushValue::Int(8)));
1890 self.sm.push("");
1891 self.emit_op(StackOp::Opcode("OP_SPLIT".into())); self.sm.pop();
1893 self.sm.pop();
1894 self.sm.push(""); self.sm.push(""); self.emit_op(StackOp::Drop); self.sm.pop();
1898 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
1902 self.sm.pop();
1903
1904 self.bring_to_top("_codePart", false);
1906 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x6a])));
1910 self.sm.push("");
1911 self.emit_op(StackOp::Opcode("OP_CAT".into()));
1912 self.sm.pop();
1913 self.sm.pop();
1914 self.sm.push("");
1915 self.emit_op(StackOp::Swap);
1918 self.sm.swap();
1919 self.emit_op(StackOp::Opcode("OP_CAT".into()));
1920 self.sm.pop();
1921 self.sm.pop();
1922 self.sm.push("");
1923 self.emit_op(StackOp::Opcode("OP_SIZE".into()));
1927 self.sm.push("");
1928 self.emit_varint_encoding();
1929
1930 self.emit_op(StackOp::Swap);
1932 self.sm.swap();
1933 self.sm.pop();
1934 self.sm.pop();
1935 self.emit_op(StackOp::Opcode("OP_CAT".into()));
1936 self.sm.push("");
1937
1938 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
1940 self.sm.push("");
1941 self.emit_op(StackOp::Swap);
1942 self.sm.swap();
1943 self.emit_op(StackOp::Opcode("OP_CAT".into()));
1944 self.sm.pop();
1945 self.sm.pop();
1946 self.sm.push("");
1947
1948 self.emit_op(StackOp::Opcode("OP_HASH256".into()));
1950
1951 self.sm.pop();
1952 self.sm.push(binding_name);
1953 self.track_depth();
1954 }
1955
1956 fn lower_compute_state_output(
1960 &mut self,
1961 binding_name: &str,
1962 args: &[String],
1963 binding_index: usize,
1964 last_uses: &std::collections::HashMap<String, usize>,
1965 ) {
1966 let preimage_ref = &args[0];
1967 let state_bytes_ref = &args[1];
1968 let new_amount_ref = &args[2];
1969
1970 let pre_last = self.is_last_use(preimage_ref, binding_index, last_uses);
1972 self.bring_to_top(preimage_ref, pre_last);
1973 self.emit_op(StackOp::Drop);
1974 self.sm.pop();
1975
1976 let amount_last = self.is_last_use(new_amount_ref, binding_index, last_uses);
1978 self.bring_to_top(new_amount_ref, amount_last);
1979 self.emit_op(StackOp::Push(PushValue::Int(8)));
1980 self.sm.push("");
1981 self.emit_op(StackOp::Opcode("OP_NUM2BIN".into()));
1982 self.sm.pop();
1983 self.sm.pop();
1984 self.sm.push("");
1985 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
1986 self.sm.pop();
1987
1988 let sb_last = self.is_last_use(state_bytes_ref, binding_index, last_uses);
1990 self.bring_to_top(state_bytes_ref, sb_last);
1991
1992 self.bring_to_top("_codePart", false);
1994 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x6a])));
1998 self.sm.push("");
1999 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2000 self.sm.pop();
2001 self.sm.pop();
2002 self.sm.push("");
2003 self.emit_op(StackOp::Swap);
2006 self.sm.swap();
2007 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2008 self.sm.pop();
2009 self.sm.pop();
2010 self.sm.push("");
2011 self.emit_op(StackOp::Opcode("OP_SIZE".into()));
2015 self.sm.push("");
2016 self.emit_varint_encoding();
2017
2018 self.emit_op(StackOp::Swap);
2020 self.sm.swap();
2021 self.sm.pop();
2022 self.sm.pop();
2023 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2024 self.sm.push("");
2025
2026 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
2028 self.sm.push("");
2029 self.emit_op(StackOp::Swap);
2030 self.sm.swap();
2031 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2032 self.sm.pop();
2033 self.sm.pop();
2034 self.sm.push("");
2035 self.sm.pop();
2038 self.sm.push(binding_name);
2039 self.track_depth();
2040 }
2041
2042 fn lower_build_change_output(
2046 &mut self,
2047 binding_name: &str,
2048 args: &[String],
2049 binding_index: usize,
2050 last_uses: &std::collections::HashMap<String, usize>,
2051 ) {
2052 let pkh_ref = &args[0];
2053 let amount_ref = &args[1];
2054
2055 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x19, 0x76, 0xa9, 0x14])));
2058 self.sm.push("");
2059
2060 let pkh_last = self.is_last_use(pkh_ref, binding_index, last_uses);
2062 self.bring_to_top(pkh_ref, pkh_last);
2063 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2065 self.sm.pop();
2066 self.sm.pop();
2067 self.sm.push("");
2068
2069 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x88, 0xac])));
2071 self.sm.push("");
2072 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2074 self.sm.pop();
2075 self.sm.pop();
2076 self.sm.push("");
2077 let amount_last = self.is_last_use(amount_ref, binding_index, last_uses);
2081 self.bring_to_top(amount_ref, amount_last);
2082 self.emit_op(StackOp::Push(PushValue::Int(8)));
2083 self.sm.push("");
2084 self.emit_op(StackOp::Opcode("OP_NUM2BIN".into()));
2085 self.sm.pop(); self.emit_op(StackOp::Swap);
2088 self.sm.swap();
2089 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2091 self.sm.pop();
2092 self.sm.pop();
2093 self.sm.push("");
2094 self.sm.pop();
2097 self.sm.push(binding_name);
2098 self.track_depth();
2099 }
2100
2101 fn lower_add_output(
2102 &mut self,
2103 binding_name: &str,
2104 satoshis: &str,
2105 state_values: &[String],
2106 _preimage: &str,
2107 binding_index: usize,
2108 last_uses: &HashMap<String, usize>,
2109 ) {
2110 let state_props: Vec<ANFProperty> = self
2116 .properties
2117 .iter()
2118 .filter(|p| !p.readonly)
2119 .cloned()
2120 .collect();
2121
2122 self.bring_to_top("_codePart", false);
2124 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0x6a])));
2128 self.sm.push("");
2129 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2130 self.sm.pop();
2131 self.sm.pop();
2132 self.sm.push("");
2133 for (i, value_ref) in state_values.iter().enumerate() {
2137 if i >= state_props.len() {
2138 break;
2139 }
2140 let prop = &state_props[i];
2141
2142 let is_last = self.is_last_use(value_ref, binding_index, last_uses);
2143 self.bring_to_top(value_ref, is_last);
2144
2145 if prop.prop_type == "bigint" {
2146 self.emit_op(StackOp::Push(PushValue::Int(8)));
2147 self.sm.push("");
2148 self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
2149 self.sm.pop();
2150 } else if prop.prop_type == "boolean" {
2151 self.emit_op(StackOp::Push(PushValue::Int(1)));
2152 self.sm.push("");
2153 self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
2154 self.sm.pop();
2155 } else if prop.prop_type == "ByteString" {
2156 self.emit_push_data_encode();
2158 }
2159
2160 self.sm.pop();
2161 self.sm.pop();
2162 self.emit_op(StackOp::Opcode("OP_CAT".to_string()));
2163 self.sm.push("");
2164 }
2165 self.emit_op(StackOp::Opcode("OP_SIZE".into())); self.sm.push("");
2170 self.emit_varint_encoding();
2171 self.emit_op(StackOp::Swap);
2175 self.sm.swap();
2176 self.sm.pop();
2177 self.sm.pop();
2178 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2179 self.sm.push("");
2180 let is_last_satoshis = self.is_last_use(satoshis, binding_index, last_uses);
2184 self.bring_to_top(satoshis, is_last_satoshis);
2185 self.emit_op(StackOp::Push(PushValue::Int(8)));
2186 self.sm.push("");
2187 self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
2188 self.sm.pop(); self.emit_op(StackOp::Swap);
2191 self.sm.swap();
2192 self.sm.pop();
2193 self.sm.pop();
2194 self.emit_op(StackOp::Opcode("OP_CAT".to_string())); self.sm.push("");
2196 self.sm.pop();
2200 self.sm.push(binding_name);
2201 self.track_depth();
2202 }
2203
2204 fn lower_add_raw_output(
2208 &mut self,
2209 binding_name: &str,
2210 satoshis: &str,
2211 script_bytes: &str,
2212 binding_index: usize,
2213 last_uses: &HashMap<String, usize>,
2214 ) {
2215 let script_is_last = self.is_last_use(script_bytes, binding_index, last_uses);
2217 self.bring_to_top(script_bytes, script_is_last);
2218
2219 self.emit_op(StackOp::Opcode("OP_SIZE".to_string())); self.sm.push("");
2222 self.emit_varint_encoding();
2223 self.emit_op(StackOp::Swap); self.sm.swap();
2228 self.sm.pop();
2229 self.sm.pop();
2230 self.emit_op(StackOp::Opcode("OP_CAT".to_string())); self.sm.push("");
2232
2233 let sat_is_last = self.is_last_use(satoshis, binding_index, last_uses);
2235 self.bring_to_top(satoshis, sat_is_last);
2236 self.emit_op(StackOp::Push(PushValue::Int(8)));
2237 self.sm.push("");
2238 self.emit_op(StackOp::Opcode("OP_NUM2BIN".to_string()));
2239 self.sm.pop(); self.emit_op(StackOp::Swap);
2242 self.sm.swap();
2243 self.sm.pop();
2244 self.sm.pop();
2245 self.emit_op(StackOp::Opcode("OP_CAT".to_string())); self.sm.push("");
2247
2248 self.sm.pop();
2250 self.sm.push(binding_name);
2251 self.track_depth();
2252 }
2253
2254 fn lower_array_literal(
2255 &mut self,
2256 binding_name: &str,
2257 elements: &[String],
2258 binding_index: usize,
2259 last_uses: &HashMap<String, usize>,
2260 ) {
2261 for elem in elements {
2265 let is_last = self.is_last_use(elem, binding_index, last_uses);
2266 self.bring_to_top(elem, is_last);
2267 self.sm.pop();
2268 self.sm.push(""); }
2270 if !elements.is_empty() {
2272 self.sm.pop();
2273 }
2274 self.sm.push(binding_name);
2275 self.track_depth();
2276 }
2277
2278 fn lower_check_multi_sig(
2279 &mut self,
2280 binding_name: &str,
2281 args: &[String],
2282 binding_index: usize,
2283 last_uses: &HashMap<String, usize>,
2284 ) {
2285 self.emit_op(StackOp::Push(PushValue::Int(0)));
2294 self.sm.push("");
2295
2296 let sigs_is_last = self.is_last_use(&args[0], binding_index, last_uses);
2298 self.bring_to_top(&args[0], sigs_is_last);
2299
2300 let pks_is_last = self.is_last_use(&args[1], binding_index, last_uses);
2302 self.bring_to_top(&args[1], pks_is_last);
2303
2304 self.sm.pop(); self.sm.pop(); self.sm.pop(); self.emit_op(StackOp::Opcode("OP_CHECKMULTISIG".to_string()));
2310 self.sm.push(binding_name);
2311 self.track_depth();
2312 }
2313
2314 fn lower_check_preimage(
2315 &mut self,
2316 binding_name: &str,
2317 preimage: &str,
2318 binding_index: usize,
2319 last_uses: &HashMap<String, usize>,
2320 ) {
2321 self.emit_op(StackOp::Opcode("OP_CODESEPARATOR".to_string()));
2357
2358 let is_last = self.is_last_use(preimage, binding_index, last_uses);
2360 self.bring_to_top(preimage, is_last);
2361
2362 self.bring_to_top("_opPushTxSig", true);
2364
2365 let g: Vec<u8> = vec![
2367 0x02, 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB,
2368 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B,
2369 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28,
2370 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17,
2371 0x98,
2372 ];
2373 self.emit_op(StackOp::Push(PushValue::Bytes(g)));
2374 self.sm.push(""); self.emit_op(StackOp::Opcode("OP_CHECKSIG".to_string()));
2378 self.sm.pop(); self.sm.pop(); self.sm.push(""); self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
2384 self.sm.pop(); self.sm.pop();
2389 self.sm.push(binding_name);
2390
2391 self.track_depth();
2392 }
2393
2394 fn lower_deserialize_state(
2403 &mut self,
2404 preimage_ref: &str,
2405 binding_index: usize,
2406 last_uses: &HashMap<String, usize>,
2407 ) {
2408 let mut prop_names: Vec<String> = Vec::new();
2409 let mut prop_types: Vec<String> = Vec::new();
2410 let mut prop_sizes: Vec<i128> = Vec::new();
2411 let mut has_variable_length = false;
2412
2413 for p in &self.properties {
2414 if p.readonly {
2415 continue;
2416 }
2417 prop_names.push(p.name.clone());
2418 prop_types.push(p.prop_type.clone());
2419 let sz: i128 = match p.prop_type.as_str() {
2420 "bigint" => 8,
2421 "boolean" => 1,
2422 "PubKey" => 33,
2423 "Addr" => 20,
2424 "Sha256" => 32,
2425 "Point" => 64,
2426 "ByteString" => { has_variable_length = true; -1 },
2427 _ => panic!("deserialize_state: unsupported type: {}", p.prop_type),
2428 };
2429 prop_sizes.push(sz);
2430 }
2431
2432 if prop_names.is_empty() {
2433 return;
2434 }
2435
2436 let is_last = self.is_last_use(preimage_ref, binding_index, last_uses);
2437 self.bring_to_top(preimage_ref, is_last);
2438
2439 self.emit_op(StackOp::Push(PushValue::Int(104)));
2441 self.sm.push("");
2442 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2443 self.sm.pop(); self.sm.pop();
2444 self.sm.push(""); self.sm.push("");
2445 self.emit_op(StackOp::Nip);
2446 self.sm.pop(); self.sm.pop();
2447 self.sm.push("");
2448
2449 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2451 self.sm.push("");
2452 self.emit_op(StackOp::Push(PushValue::Int(44)));
2453 self.sm.push("");
2454 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2455 self.sm.pop(); self.sm.pop();
2456 self.sm.push("");
2457 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2458 self.sm.pop(); self.sm.pop();
2459 self.sm.push(""); self.sm.push("");
2460 self.emit_op(StackOp::Drop);
2461 self.sm.pop();
2462
2463 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2465 self.sm.push("");
2466 self.emit_op(StackOp::Push(PushValue::Int(8)));
2467 self.sm.push("");
2468 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2469 self.sm.pop(); self.sm.pop();
2470 self.sm.push("");
2471 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2472 self.sm.pop(); self.sm.pop();
2473 self.sm.push(""); self.sm.push("");
2474 self.emit_op(StackOp::Drop);
2475 self.sm.pop();
2476
2477 if !has_variable_length {
2478 let state_len: i128 = prop_sizes.iter().sum();
2479
2480 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2482 self.sm.push("");
2483 self.emit_op(StackOp::Push(PushValue::Int(state_len)));
2484 self.sm.push("");
2485 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2486 self.sm.pop(); self.sm.pop();
2487 self.sm.push("");
2488 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2489 self.sm.pop(); self.sm.pop();
2490 self.sm.push(""); self.sm.push("");
2491 self.emit_op(StackOp::Nip);
2492 self.sm.pop(); self.sm.pop();
2493 self.sm.push("");
2494
2495 self.split_fixed_state_fields(&prop_names, &prop_types, &prop_sizes);
2497 } else if !self.sm.has("_codePart") {
2498 self.emit_op(StackOp::Drop);
2500 self.sm.pop();
2501 } else {
2502 self.emit_op(StackOp::Push(PushValue::Int(1)));
2504 self.sm.push("");
2505 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
2506 self.sm.pop(); self.sm.pop();
2507 self.sm.push(""); self.sm.push("");
2508 self.emit_op(StackOp::Swap);
2509 self.sm.swap();
2510 self.emit_op(StackOp::Dup);
2511 self.sm.push("");
2512 self.emit_op(StackOp::Push(PushValue::Bytes(vec![0])));
2514 self.sm.push("");
2515 self.emit_op(StackOp::Opcode("OP_CAT".into()));
2516 self.sm.pop(); self.sm.pop();
2517 self.sm.push("");
2518 self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
2519 self.emit_op(StackOp::Push(PushValue::Int(253)));
2520 self.sm.push("");
2521 self.emit_op(StackOp::Opcode("OP_LESSTHAN".into()));
2522 self.sm.pop(); self.sm.pop();
2523 self.sm.push("");
2524
2525 self.emit_op(StackOp::Opcode("OP_IF".into()));
2526 self.sm.pop();
2527 let sm_at_varint_if = self.sm.clone();
2528 self.emit_op(StackOp::Drop);
2529 self.sm.pop();
2530
2531 self.emit_op(StackOp::Opcode("OP_ELSE".into()));
2532 self.sm = sm_at_varint_if.clone();
2533 self.emit_op(StackOp::Drop);
2534 self.sm.pop();
2535 self.emit_op(StackOp::Push(PushValue::Int(2)));
2536 self.sm.push("");
2537 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
2538 self.sm.pop(); self.sm.pop();
2539 self.sm.push(""); self.sm.push("");
2540 self.emit_op(StackOp::Nip);
2541 self.sm.pop(); self.sm.pop();
2542 self.sm.push("");
2543
2544 self.emit_op(StackOp::Opcode("OP_ENDIF".into()));
2545
2546 self.bring_to_top("_codePart", false);
2548 self.emit_op(StackOp::Opcode("OP_SIZE".into()));
2549 self.sm.push("");
2550 self.emit_op(StackOp::Nip);
2551 self.sm.pop(); self.sm.pop();
2552 self.sm.push("");
2553 self.emit_op(StackOp::PushCodeSepIndex);
2554 self.sm.push("");
2555 self.emit_op(StackOp::Opcode("OP_SUB".into()));
2556 self.sm.pop(); self.sm.pop();
2557 self.sm.push("");
2558
2559 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
2561 self.sm.pop(); self.sm.pop();
2562 self.sm.push(""); self.sm.push("");
2563 self.emit_op(StackOp::Nip);
2564 self.sm.pop(); self.sm.pop();
2565 self.sm.push("");
2566
2567 self.parse_variable_length_state_fields(&prop_names, &prop_types, &prop_sizes);
2569 }
2570
2571 self.track_depth();
2572 }
2573
2574 fn split_fixed_state_fields(
2575 &mut self,
2576 prop_names: &[String],
2577 prop_types: &[String],
2578 prop_sizes: &[i128],
2579 ) {
2580 let num_props = prop_names.len();
2581 if num_props == 1 {
2582 if prop_types[0] == "bigint" || prop_types[0] == "boolean" {
2583 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2584 }
2585 self.sm.pop();
2586 self.sm.push(&prop_names[0]);
2587 } else {
2588 for i in 0..num_props {
2589 let sz = prop_sizes[i];
2590 if i < num_props - 1 {
2591 self.emit_op(StackOp::Push(PushValue::Int(sz)));
2592 self.sm.push("");
2593 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2594 self.sm.pop(); self.sm.pop();
2595 self.sm.push(""); self.sm.push("");
2596 self.emit_op(StackOp::Swap);
2597 self.sm.swap();
2598 if prop_types[i] == "bigint" || prop_types[i] == "boolean" {
2599 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2600 }
2601 self.emit_op(StackOp::Swap);
2602 self.sm.swap();
2603 self.sm.pop(); self.sm.pop();
2604 self.sm.push(&prop_names[i]);
2605 self.sm.push("");
2606 } else {
2607 if prop_types[i] == "bigint" || prop_types[i] == "boolean" {
2608 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2609 }
2610 self.sm.pop();
2611 self.sm.push(&prop_names[i]);
2612 }
2613 }
2614 }
2615 }
2616
2617 fn parse_variable_length_state_fields(
2618 &mut self,
2619 prop_names: &[String],
2620 prop_types: &[String],
2621 prop_sizes: &[i128],
2622 ) {
2623 let num_props = prop_names.len();
2624 if num_props == 1 {
2625 if prop_types[0] == "ByteString" {
2626 self.emit_push_data_decode(); self.emit_op(StackOp::Drop); self.sm.pop();
2629 } else if prop_types[0] == "bigint" || prop_types[0] == "boolean" {
2630 self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
2631 }
2632 self.sm.pop();
2633 self.sm.push(&prop_names[0]);
2634 } else {
2635 for i in 0..num_props {
2636 if i < num_props - 1 {
2637 if prop_types[i] == "ByteString" {
2638 self.emit_push_data_decode(); self.sm.pop(); self.sm.pop();
2641 self.sm.push(&prop_names[i]);
2642 self.sm.push(""); } else {
2644 self.emit_op(StackOp::Push(PushValue::Int(prop_sizes[i])));
2645 self.sm.push("");
2646 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
2647 self.sm.pop(); self.sm.pop();
2648 self.sm.push(""); self.sm.push("");
2649 self.emit_op(StackOp::Swap); self.sm.swap();
2650 if prop_types[i] == "bigint" || prop_types[i] == "boolean" {
2651 self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
2652 }
2653 self.emit_op(StackOp::Swap); self.sm.swap();
2654 self.sm.pop(); self.sm.pop();
2655 self.sm.push(&prop_names[i]);
2656 self.sm.push("");
2657 }
2658 } else {
2659 if prop_types[i] == "ByteString" {
2660 self.emit_push_data_decode(); self.emit_op(StackOp::Drop); self.sm.pop();
2663 } else if prop_types[i] == "bigint" || prop_types[i] == "boolean" {
2664 self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
2665 }
2666 self.sm.pop();
2667 self.sm.push(&prop_names[i]);
2668 }
2669 }
2670 }
2671 }
2672
2673 fn lower_extractor(
2691 &mut self,
2692 binding_name: &str,
2693 func_name: &str,
2694 args: &[String],
2695 binding_index: usize,
2696 last_uses: &HashMap<String, usize>,
2697 ) {
2698 assert!(!args.is_empty(), "{} requires 1 argument", func_name);
2699 let is_last = self.is_last_use(&args[0], binding_index, last_uses);
2700 self.bring_to_top(&args[0], is_last);
2701
2702 self.sm.pop(); match func_name {
2706 "extractVersion" => {
2707 self.emit_op(StackOp::Push(PushValue::Int(4)));
2709 self.sm.push("");
2710 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2711 self.sm.pop();
2712 self.sm.push("");
2713 self.sm.push("");
2714 self.emit_op(StackOp::Drop);
2715 self.sm.pop();
2716 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2717 }
2718 "extractHashPrevouts" => {
2719 self.emit_op(StackOp::Push(PushValue::Int(4)));
2721 self.sm.push("");
2722 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2723 self.sm.pop();
2724 self.sm.push("");
2725 self.sm.push("");
2726 self.emit_op(StackOp::Nip);
2727 self.sm.pop();
2728 self.sm.pop();
2729 self.sm.push("");
2730 self.emit_op(StackOp::Push(PushValue::Int(32)));
2731 self.sm.push("");
2732 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2733 self.sm.pop(); self.sm.pop(); self.sm.push("");
2736 self.sm.push("");
2737 self.emit_op(StackOp::Drop);
2738 self.sm.pop();
2739 }
2740 "extractHashSequence" => {
2741 self.emit_op(StackOp::Push(PushValue::Int(36)));
2743 self.sm.push("");
2744 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2745 self.sm.pop();
2746 self.sm.push("");
2747 self.sm.push("");
2748 self.emit_op(StackOp::Nip);
2749 self.sm.pop();
2750 self.sm.pop();
2751 self.sm.push("");
2752 self.emit_op(StackOp::Push(PushValue::Int(32)));
2753 self.sm.push("");
2754 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2755 self.sm.pop(); self.sm.pop(); self.sm.push("");
2758 self.sm.push("");
2759 self.emit_op(StackOp::Drop);
2760 self.sm.pop();
2761 }
2762 "extractOutpoint" => {
2763 self.emit_op(StackOp::Push(PushValue::Int(68)));
2765 self.sm.push("");
2766 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2767 self.sm.pop();
2768 self.sm.push("");
2769 self.sm.push("");
2770 self.emit_op(StackOp::Nip);
2771 self.sm.pop();
2772 self.sm.pop();
2773 self.sm.push("");
2774 self.emit_op(StackOp::Push(PushValue::Int(36)));
2775 self.sm.push("");
2776 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2777 self.sm.pop(); self.sm.pop(); self.sm.push("");
2780 self.sm.push("");
2781 self.emit_op(StackOp::Drop);
2782 self.sm.pop();
2783 }
2784 "extractSigHashType" => {
2785 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2788 self.sm.push("");
2789 self.sm.push("");
2790 self.emit_op(StackOp::Push(PushValue::Int(4)));
2791 self.sm.push("");
2792 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2793 self.sm.pop();
2794 self.sm.pop();
2795 self.sm.push("");
2796 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2797 self.sm.pop();
2798 self.sm.pop();
2799 self.sm.push("");
2800 self.sm.push("");
2801 self.emit_op(StackOp::Nip);
2802 self.sm.pop();
2803 self.sm.pop();
2804 self.sm.push("");
2805 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2806 }
2807 "extractLocktime" => {
2808 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2811 self.sm.push("");
2812 self.sm.push("");
2813 self.emit_op(StackOp::Push(PushValue::Int(8)));
2814 self.sm.push("");
2815 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2816 self.sm.pop();
2817 self.sm.pop();
2818 self.sm.push("");
2819 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2820 self.sm.pop();
2821 self.sm.pop();
2822 self.sm.push("");
2823 self.sm.push("");
2824 self.emit_op(StackOp::Nip);
2825 self.sm.pop();
2826 self.sm.pop();
2827 self.sm.push("");
2828 self.emit_op(StackOp::Push(PushValue::Int(4)));
2829 self.sm.push("");
2830 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2831 self.sm.pop(); self.sm.pop(); self.sm.push("");
2834 self.sm.push("");
2835 self.emit_op(StackOp::Drop);
2836 self.sm.pop();
2837 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2838 }
2839 "extractOutputHash" | "extractOutputs" => {
2840 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2843 self.sm.push("");
2844 self.sm.push("");
2845 self.emit_op(StackOp::Push(PushValue::Int(40)));
2846 self.sm.push("");
2847 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2848 self.sm.pop();
2849 self.sm.pop();
2850 self.sm.push("");
2851 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2852 self.sm.pop();
2853 self.sm.pop();
2854 self.sm.push("");
2855 self.sm.push("");
2856 self.emit_op(StackOp::Nip);
2857 self.sm.pop();
2858 self.sm.pop();
2859 self.sm.push("");
2860 self.emit_op(StackOp::Push(PushValue::Int(32)));
2861 self.sm.push("");
2862 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2863 self.sm.pop(); self.sm.pop(); self.sm.push("");
2866 self.sm.push("");
2867 self.emit_op(StackOp::Drop);
2868 self.sm.pop();
2869 }
2870 "extractAmount" => {
2871 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2874 self.sm.push("");
2875 self.sm.push("");
2876 self.emit_op(StackOp::Push(PushValue::Int(52)));
2877 self.sm.push("");
2878 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2879 self.sm.pop();
2880 self.sm.pop();
2881 self.sm.push("");
2882 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2883 self.sm.pop();
2884 self.sm.pop();
2885 self.sm.push("");
2886 self.sm.push("");
2887 self.emit_op(StackOp::Nip);
2888 self.sm.pop();
2889 self.sm.pop();
2890 self.sm.push("");
2891 self.emit_op(StackOp::Push(PushValue::Int(8)));
2892 self.sm.push("");
2893 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2894 self.sm.pop(); self.sm.pop(); self.sm.push("");
2897 self.sm.push("");
2898 self.emit_op(StackOp::Drop);
2899 self.sm.pop();
2900 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2901 }
2902 "extractSequence" => {
2903 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2906 self.sm.push("");
2907 self.sm.push("");
2908 self.emit_op(StackOp::Push(PushValue::Int(44)));
2909 self.sm.push("");
2910 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2911 self.sm.pop();
2912 self.sm.pop();
2913 self.sm.push("");
2914 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2915 self.sm.pop();
2916 self.sm.pop();
2917 self.sm.push("");
2918 self.sm.push("");
2919 self.emit_op(StackOp::Nip);
2920 self.sm.pop();
2921 self.sm.pop();
2922 self.sm.push("");
2923 self.emit_op(StackOp::Push(PushValue::Int(4)));
2924 self.sm.push("");
2925 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2926 self.sm.pop(); self.sm.pop(); self.sm.push("");
2929 self.sm.push("");
2930 self.emit_op(StackOp::Drop);
2931 self.sm.pop();
2932 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2933 }
2934 "extractScriptCode" => {
2935 self.emit_op(StackOp::Push(PushValue::Int(104)));
2938 self.sm.push("");
2939 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2940 self.sm.pop();
2941 self.sm.push("");
2942 self.sm.push("");
2943 self.emit_op(StackOp::Nip);
2944 self.sm.pop();
2945 self.sm.pop();
2946 self.sm.push("");
2947 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
2948 self.sm.push("");
2949 self.emit_op(StackOp::Push(PushValue::Int(52)));
2950 self.sm.push("");
2951 self.emit_op(StackOp::Opcode("OP_SUB".to_string()));
2952 self.sm.pop();
2953 self.sm.pop();
2954 self.sm.push("");
2955 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2956 self.sm.pop();
2957 self.sm.pop();
2958 self.sm.push("");
2959 self.sm.push("");
2960 self.emit_op(StackOp::Drop);
2961 self.sm.pop();
2962 }
2963 "extractInputIndex" => {
2964 self.emit_op(StackOp::Push(PushValue::Int(100)));
2967 self.sm.push("");
2968 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2969 self.sm.pop();
2970 self.sm.push("");
2971 self.sm.push("");
2972 self.emit_op(StackOp::Nip);
2973 self.sm.pop();
2974 self.sm.pop();
2975 self.sm.push("");
2976 self.emit_op(StackOp::Push(PushValue::Int(4)));
2977 self.sm.push("");
2978 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
2979 self.sm.pop(); self.sm.pop(); self.sm.push("");
2982 self.sm.push("");
2983 self.emit_op(StackOp::Drop);
2984 self.sm.pop();
2985 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
2986 }
2987 _ => panic!("unknown extractor: {}", func_name),
2988 }
2989
2990 self.sm.pop();
2992 self.sm.push(binding_name);
2993 self.track_depth();
2994 }
2995
2996 fn lower_array_access(
3010 &mut self,
3011 binding_name: &str,
3012 args: &[String],
3013 binding_index: usize,
3014 last_uses: &HashMap<String, usize>,
3015 ) {
3016 assert!(args.len() >= 2, "__array_access requires 2 arguments (object, index)");
3017
3018 let obj = &args[0];
3019 let index = &args[1];
3020
3021 let obj_is_last = self.is_last_use(obj, binding_index, last_uses);
3023 self.bring_to_top(obj, obj_is_last);
3024
3025 let index_is_last = self.is_last_use(index, binding_index, last_uses);
3027 self.bring_to_top(index, index_is_last);
3028
3029 self.sm.pop(); self.sm.pop(); self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
3033 self.sm.push(""); self.sm.push(""); self.emit_op(StackOp::Nip);
3038 self.sm.pop();
3039 let right_part = self.sm.pop();
3040 self.sm.push(&right_part);
3041
3042 self.emit_op(StackOp::Push(PushValue::Int(1)));
3044 self.sm.push("");
3045
3046 self.sm.pop(); self.sm.pop(); self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
3050 self.sm.push(""); self.sm.push(""); self.emit_op(StackOp::Drop);
3055 self.sm.pop();
3056 self.sm.pop();
3057 self.sm.push("");
3058
3059 self.sm.pop();
3061 self.emit_op(StackOp::Opcode("OP_BIN2NUM".to_string()));
3062
3063 self.sm.push(binding_name);
3064 self.track_depth();
3065 }
3066
3067 fn lower_reverse_bytes(
3068 &mut self,
3069 binding_name: &str,
3070 args: &[String],
3071 binding_index: usize,
3072 last_uses: &HashMap<String, usize>,
3073 ) {
3074 assert!(!args.is_empty(), "reverseBytes requires 1 argument");
3075 let is_last = self.is_last_use(&args[0], binding_index, last_uses);
3076 self.bring_to_top(&args[0], is_last);
3077
3078 self.sm.pop();
3082
3083 self.emit_op(StackOp::Push(PushValue::Int(0)));
3085 self.emit_op(StackOp::Swap);
3086
3087 for _ in 0..520 {
3089 self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
3091 self.emit_op(StackOp::Opcode("OP_SIZE".to_string()));
3092 self.emit_op(StackOp::Nip);
3093 self.emit_op(StackOp::If {
3094 then_ops: vec![
3095 StackOp::Push(PushValue::Int(1)),
3096 StackOp::Opcode("OP_SPLIT".to_string()),
3097 StackOp::Swap,
3098 StackOp::Rot,
3099 StackOp::Opcode("OP_CAT".to_string()),
3100 StackOp::Swap,
3101 ],
3102 else_ops: vec![],
3103 });
3104 }
3105
3106 self.emit_op(StackOp::Drop);
3108
3109 self.sm.push(binding_name);
3110 self.track_depth();
3111 }
3112
3113 fn lower_substr(
3114 &mut self,
3115 binding_name: &str,
3116 args: &[String],
3117 binding_index: usize,
3118 last_uses: &HashMap<String, usize>,
3119 ) {
3120 assert!(args.len() >= 3, "substr requires 3 arguments");
3121
3122 let data = &args[0];
3123 let start = &args[1];
3124 let length = &args[2];
3125
3126 let data_is_last = self.is_last_use(data, binding_index, last_uses);
3127 self.bring_to_top(data, data_is_last);
3128
3129 let start_is_last = self.is_last_use(start, binding_index, last_uses);
3130 self.bring_to_top(start, start_is_last);
3131
3132 self.sm.pop();
3133 self.sm.pop();
3134 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
3135 self.sm.push("");
3136 self.sm.push("");
3137
3138 self.emit_op(StackOp::Nip);
3139 self.sm.pop();
3140 let right_part = self.sm.pop();
3141 self.sm.push(&right_part);
3142
3143 let len_is_last = self.is_last_use(length, binding_index, last_uses);
3144 self.bring_to_top(length, len_is_last);
3145
3146 self.sm.pop();
3147 self.sm.pop();
3148 self.emit_op(StackOp::Opcode("OP_SPLIT".to_string()));
3149 self.sm.push("");
3150 self.sm.push("");
3151
3152 self.emit_op(StackOp::Drop);
3153 self.sm.pop();
3154 self.sm.pop();
3155
3156 self.sm.push(binding_name);
3157 self.track_depth();
3158 }
3159 fn lower_verify_rabin_sig(
3160 &mut self,
3161 binding_name: &str,
3162 args: &[String],
3163 binding_index: usize,
3164 last_uses: &HashMap<String, usize>,
3165 ) {
3166 assert!(args.len() >= 4, "verifyRabinSig requires 4 arguments");
3167
3168 let msg = &args[0];
3172 let sig = &args[1];
3173 let padding = &args[2];
3174 let pub_key = &args[3];
3175
3176 let msg_is_last = self.is_last_use(msg, binding_index, last_uses);
3177 self.bring_to_top(msg, msg_is_last);
3178
3179 let sig_is_last = self.is_last_use(sig, binding_index, last_uses);
3180 self.bring_to_top(sig, sig_is_last);
3181
3182 let padding_is_last = self.is_last_use(padding, binding_index, last_uses);
3183 self.bring_to_top(padding, padding_is_last);
3184
3185 let pub_key_is_last = self.is_last_use(pub_key, binding_index, last_uses);
3186 self.bring_to_top(pub_key, pub_key_is_last);
3187
3188 self.sm.pop();
3190 self.sm.pop();
3191 self.sm.pop();
3192 self.sm.pop();
3193
3194 self.emit_op(StackOp::Opcode("OP_SWAP".to_string())); self.emit_op(StackOp::Opcode("OP_ROT".to_string())); self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
3199 self.emit_op(StackOp::Opcode("OP_MUL".to_string())); self.emit_op(StackOp::Opcode("OP_ADD".to_string())); self.emit_op(StackOp::Opcode("OP_SWAP".to_string())); self.emit_op(StackOp::Opcode("OP_MOD".to_string())); self.emit_op(StackOp::Opcode("OP_SWAP".to_string())); self.emit_op(StackOp::Opcode("OP_SHA256".to_string()));
3205 self.emit_op(StackOp::Opcode("OP_EQUAL".to_string()));
3206
3207 self.sm.push(binding_name);
3208 self.track_depth();
3209 }
3210
3211 fn lower_sign(
3214 &mut self,
3215 binding_name: &str,
3216 args: &[String],
3217 binding_index: usize,
3218 last_uses: &HashMap<String, usize>,
3219 ) {
3220 assert!(!args.is_empty(), "sign requires 1 argument");
3221 let x = &args[0];
3222
3223 let x_is_last = self.is_last_use(x, binding_index, last_uses);
3224 self.bring_to_top(x, x_is_last);
3225 self.sm.pop();
3226
3227 self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
3228 self.emit_op(StackOp::If {
3229 then_ops: vec![
3230 StackOp::Opcode("OP_DUP".to_string()),
3231 StackOp::Opcode("OP_ABS".to_string()),
3232 StackOp::Swap,
3233 StackOp::Opcode("OP_DIV".to_string()),
3234 ],
3235 else_ops: vec![],
3236 });
3237
3238 self.sm.push(binding_name);
3239 self.track_depth();
3240 }
3241
3242 fn lower_right(
3245 &mut self,
3246 binding_name: &str,
3247 args: &[String],
3248 binding_index: usize,
3249 last_uses: &HashMap<String, usize>,
3250 ) {
3251 assert!(args.len() >= 2, "right requires 2 arguments");
3252 let data = &args[0];
3253 let length = &args[1];
3254
3255 let data_is_last = self.is_last_use(data, binding_index, last_uses);
3256 self.bring_to_top(data, data_is_last);
3257
3258 let length_is_last = self.is_last_use(length, binding_index, last_uses);
3259 self.bring_to_top(length, length_is_last);
3260
3261 self.sm.pop(); self.sm.pop(); self.emit_op(StackOp::Swap); self.emit_op(StackOp::Opcode("OP_SIZE".to_string())); self.emit_op(StackOp::Rot); self.emit_op(StackOp::Opcode("OP_SUB".to_string())); self.emit_op(StackOp::Opcode("OP_SPLIT".to_string())); self.emit_op(StackOp::Nip); self.sm.push(binding_name);
3272 self.track_depth();
3273 }
3274
3275 fn emit_wots_one_chain(&mut self, chain_index: usize) {
3279 self.emit_op(StackOp::Opcode("OP_DUP".into()));
3281 self.emit_op(StackOp::Push(PushValue::Int(15)));
3282 self.emit_op(StackOp::Swap);
3283 self.emit_op(StackOp::Opcode("OP_SUB".into()));
3284 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); self.emit_op(StackOp::Swap);
3288 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); self.emit_op(StackOp::Swap);
3290 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); self.emit_op(StackOp::Swap);
3295 self.emit_op(StackOp::Push(PushValue::Int(32)));
3296 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
3297 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); self.emit_op(StackOp::Swap);
3299 for j in 0..15usize {
3305 let adrs_bytes = vec![chain_index as u8, j as u8];
3306 self.emit_op(StackOp::Opcode("OP_DUP".into()));
3307 self.emit_op(StackOp::Opcode("OP_0NOTEQUAL".into()));
3308 self.emit_op(StackOp::If {
3309 then_ops: vec![
3310 StackOp::Opcode("OP_1SUB".into()), ],
3312 else_ops: vec![
3313 StackOp::Swap, StackOp::Push(PushValue::Int(2)),
3315 StackOp::Opcode("OP_PICK".into()), StackOp::Push(PushValue::Bytes(adrs_bytes)), StackOp::Opcode("OP_CAT".into()), StackOp::Swap, StackOp::Opcode("OP_CAT".into()), StackOp::Opcode("OP_SHA256".into()), StackOp::Swap, ],
3323 });
3324 }
3325 self.emit_op(StackOp::Drop); self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3330 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3331 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3332 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3333
3334 self.emit_op(StackOp::Rot);
3336 self.emit_op(StackOp::Opcode("OP_ADD".into()));
3337
3338 self.emit_op(StackOp::Swap);
3340 self.emit_op(StackOp::Push(PushValue::Int(3)));
3341 self.emit_op(StackOp::Opcode("OP_ROLL".into()));
3342 self.emit_op(StackOp::Opcode("OP_CAT".into()));
3343 }
3344
3345 fn lower_verify_wots(
3349 &mut self,
3350 binding_name: &str,
3351 args: &[String],
3352 binding_index: usize,
3353 last_uses: &HashMap<String, usize>,
3354 ) {
3355 assert!(args.len() >= 3, "verifyWOTS requires 3 arguments: msg, sig, pubkey");
3356
3357 for arg in args.iter() {
3358 let is_last = self.is_last_use(arg, binding_index, last_uses);
3359 self.bring_to_top(arg, is_last);
3360 }
3361 for _ in 0..3 { self.sm.pop(); }
3362 self.emit_op(StackOp::Push(PushValue::Int(32)));
3366 self.emit_op(StackOp::Opcode("OP_SPLIT".into())); self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into())); self.emit_op(StackOp::Rot); self.emit_op(StackOp::Rot); self.emit_op(StackOp::Swap); self.emit_op(StackOp::Opcode("OP_SHA256".into())); self.emit_op(StackOp::Swap);
3377 self.emit_op(StackOp::Push(PushValue::Int(0)));
3378 self.emit_op(StackOp::Opcode("OP_0".into()));
3379 self.emit_op(StackOp::Push(PushValue::Int(3)));
3380 self.emit_op(StackOp::Opcode("OP_ROLL".into()));
3381
3382 for byte_idx in 0..32 {
3384 if byte_idx < 31 {
3385 self.emit_op(StackOp::Push(PushValue::Int(1)));
3386 self.emit_op(StackOp::Opcode("OP_SPLIT".into()));
3387 self.emit_op(StackOp::Swap);
3388 }
3389 self.emit_op(StackOp::Push(PushValue::Int(0)));
3391 self.emit_op(StackOp::Push(PushValue::Int(1)));
3392 self.emit_op(StackOp::Opcode("OP_NUM2BIN".into()));
3393 self.emit_op(StackOp::Opcode("OP_CAT".into()));
3394 self.emit_op(StackOp::Opcode("OP_BIN2NUM".into()));
3395 self.emit_op(StackOp::Opcode("OP_DUP".into()));
3397 self.emit_op(StackOp::Push(PushValue::Int(16)));
3398 self.emit_op(StackOp::Opcode("OP_DIV".into()));
3399 self.emit_op(StackOp::Swap);
3400 self.emit_op(StackOp::Push(PushValue::Int(16)));
3401 self.emit_op(StackOp::Opcode("OP_MOD".into()));
3402
3403 if byte_idx < 31 {
3404 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
3405 self.emit_op(StackOp::Swap);
3406 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
3407 } else {
3408 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
3409 }
3410
3411 self.emit_wots_one_chain(byte_idx * 2); if byte_idx < 31 {
3414 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3415 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3416 self.emit_op(StackOp::Swap);
3417 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
3418 } else {
3419 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3420 }
3421
3422 self.emit_wots_one_chain(byte_idx * 2 + 1); if byte_idx < 31 {
3425 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3426 }
3427 }
3428
3429 self.emit_op(StackOp::Swap);
3431 self.emit_op(StackOp::Opcode("OP_DUP".into()));
3433 self.emit_op(StackOp::Push(PushValue::Int(16)));
3434 self.emit_op(StackOp::Opcode("OP_MOD".into()));
3435 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
3436 self.emit_op(StackOp::Opcode("OP_DUP".into()));
3438 self.emit_op(StackOp::Push(PushValue::Int(16)));
3439 self.emit_op(StackOp::Opcode("OP_DIV".into()));
3440 self.emit_op(StackOp::Push(PushValue::Int(16)));
3441 self.emit_op(StackOp::Opcode("OP_MOD".into()));
3442 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
3443 self.emit_op(StackOp::Push(PushValue::Int(256)));
3445 self.emit_op(StackOp::Opcode("OP_DIV".into()));
3446 self.emit_op(StackOp::Push(PushValue::Int(16)));
3447 self.emit_op(StackOp::Opcode("OP_MOD".into()));
3448 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
3449
3450 for ci in 0..3 {
3452 self.emit_op(StackOp::Opcode("OP_TOALTSTACK".into()));
3453 self.emit_op(StackOp::Push(PushValue::Int(0)));
3454 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3455 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into()));
3456 self.emit_wots_one_chain(64 + ci);
3457 self.emit_op(StackOp::Swap);
3458 self.emit_op(StackOp::Drop);
3459 }
3460
3461 self.emit_op(StackOp::Swap);
3463 self.emit_op(StackOp::Drop);
3464 self.emit_op(StackOp::Opcode("OP_SHA256".into()));
3466 self.emit_op(StackOp::Opcode("OP_FROMALTSTACK".into())); self.emit_op(StackOp::Opcode("OP_EQUAL".into()));
3468 self.emit_op(StackOp::Swap);
3470 self.emit_op(StackOp::Drop);
3471
3472 self.sm.push(binding_name);
3473 self.track_depth();
3474 }
3475
3476 fn lower_verify_slh_dsa(
3480 &mut self,
3481 binding_name: &str,
3482 param_key: &str,
3483 args: &[String],
3484 binding_index: usize,
3485 last_uses: &HashMap<String, usize>,
3486 ) {
3487 assert!(
3488 args.len() >= 3,
3489 "verifySLHDSA requires 3 arguments: msg, sig, pubkey"
3490 );
3491
3492 for arg in args.iter() {
3494 let is_last = self.is_last_use(arg, binding_index, last_uses);
3495 self.bring_to_top(arg, is_last);
3496 }
3497 for _ in 0..3 {
3498 self.sm.pop();
3499 }
3500
3501 super::slh_dsa::emit_verify_slh_dsa(&mut |op| self.ops.push(op), param_key);
3503
3504 self.sm.push(binding_name);
3505 self.track_depth();
3506 }
3507
3508 fn lower_sha256_compress(
3513 &mut self,
3514 binding_name: &str,
3515 args: &[String],
3516 binding_index: usize,
3517 last_uses: &HashMap<String, usize>,
3518 ) {
3519 assert!(
3520 args.len() >= 2,
3521 "sha256Compress requires 2 arguments: state, block"
3522 );
3523 for arg in args.iter() {
3524 let is_last = self.is_last_use(arg, binding_index, last_uses);
3525 self.bring_to_top(arg, is_last);
3526 }
3527 for _ in 0..2 {
3528 self.sm.pop();
3529 }
3530
3531 super::sha256::emit_sha256_compress(&mut |op| self.ops.push(op));
3532
3533 self.sm.push(binding_name);
3534 self.track_depth();
3535 }
3536
3537 fn lower_sha256_finalize(
3538 &mut self,
3539 binding_name: &str,
3540 args: &[String],
3541 binding_index: usize,
3542 last_uses: &HashMap<String, usize>,
3543 ) {
3544 assert!(
3545 args.len() >= 3,
3546 "sha256Finalize requires 3 arguments: state, remaining, msgBitLen"
3547 );
3548 for arg in args.iter() {
3549 let is_last = self.is_last_use(arg, binding_index, last_uses);
3550 self.bring_to_top(arg, is_last);
3551 }
3552 for _ in 0..3 {
3553 self.sm.pop();
3554 }
3555
3556 super::sha256::emit_sha256_finalize(&mut |op| self.ops.push(op));
3557
3558 self.sm.push(binding_name);
3559 self.track_depth();
3560 }
3561
3562 fn lower_blake3_compress(
3563 &mut self,
3564 binding_name: &str,
3565 args: &[String],
3566 binding_index: usize,
3567 last_uses: &HashMap<String, usize>,
3568 ) {
3569 assert!(
3570 args.len() >= 2,
3571 "blake3Compress requires 2 arguments: chainingValue, block"
3572 );
3573 for arg in args.iter() {
3574 let is_last = self.is_last_use(arg, binding_index, last_uses);
3575 self.bring_to_top(arg, is_last);
3576 }
3577 for _ in 0..2 {
3578 self.sm.pop();
3579 }
3580
3581 super::blake3::emit_blake3_compress(&mut |op| self.ops.push(op));
3582
3583 self.sm.push(binding_name);
3584 self.track_depth();
3585 }
3586
3587 fn lower_blake3_hash(
3588 &mut self,
3589 binding_name: &str,
3590 args: &[String],
3591 binding_index: usize,
3592 last_uses: &HashMap<String, usize>,
3593 ) {
3594 assert!(
3595 args.len() >= 1,
3596 "blake3Hash requires 1 argument: message"
3597 );
3598 for arg in args.iter() {
3599 let is_last = self.is_last_use(arg, binding_index, last_uses);
3600 self.bring_to_top(arg, is_last);
3601 }
3602 for _ in 0..1 {
3603 self.sm.pop();
3604 }
3605
3606 super::blake3::emit_blake3_hash(&mut |op| self.ops.push(op));
3607
3608 self.sm.push(binding_name);
3609 self.track_depth();
3610 }
3611
3612 fn lower_ec_builtin(
3613 &mut self,
3614 binding_name: &str,
3615 func_name: &str,
3616 args: &[String],
3617 binding_index: usize,
3618 last_uses: &HashMap<String, usize>,
3619 ) {
3620 for arg in args.iter() {
3622 let is_last = self.is_last_use(arg, binding_index, last_uses);
3623 self.bring_to_top(arg, is_last);
3624 }
3625 for _ in args {
3626 self.sm.pop();
3627 }
3628
3629 let emit = &mut |op: StackOp| self.ops.push(op);
3630
3631 match func_name {
3632 "ecAdd" => super::ec::emit_ec_add(emit),
3633 "ecMul" => super::ec::emit_ec_mul(emit),
3634 "ecMulGen" => super::ec::emit_ec_mul_gen(emit),
3635 "ecNegate" => super::ec::emit_ec_negate(emit),
3636 "ecOnCurve" => super::ec::emit_ec_on_curve(emit),
3637 "ecModReduce" => super::ec::emit_ec_mod_reduce(emit),
3638 "ecEncodeCompressed" => super::ec::emit_ec_encode_compressed(emit),
3639 "ecMakePoint" => super::ec::emit_ec_make_point(emit),
3640 "ecPointX" => super::ec::emit_ec_point_x(emit),
3641 "ecPointY" => super::ec::emit_ec_point_y(emit),
3642 _ => panic!("unknown EC builtin: {}", func_name),
3643 }
3644
3645 self.sm.push(binding_name);
3646 self.track_depth();
3647 }
3648
3649 fn lower_safediv(
3652 &mut self,
3653 binding_name: &str,
3654 args: &[String],
3655 binding_index: usize,
3656 last_uses: &HashMap<String, usize>,
3657 ) {
3658 assert!(args.len() >= 2, "safediv requires 2 arguments");
3659
3660 let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3661 self.bring_to_top(&args[0], a_is_last);
3662
3663 let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3664 self.bring_to_top(&args[1], b_is_last);
3665
3666 self.sm.pop();
3667 self.sm.pop();
3668
3669 self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
3670 self.emit_op(StackOp::Opcode("OP_0NOTEQUAL".to_string()));
3671 self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
3672 self.emit_op(StackOp::Opcode("OP_DIV".to_string()));
3673
3674 self.sm.push(binding_name);
3675 self.track_depth();
3676 }
3677
3678 fn lower_safemod(
3681 &mut self,
3682 binding_name: &str,
3683 args: &[String],
3684 binding_index: usize,
3685 last_uses: &HashMap<String, usize>,
3686 ) {
3687 assert!(args.len() >= 2, "safemod requires 2 arguments");
3688
3689 let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3690 self.bring_to_top(&args[0], a_is_last);
3691
3692 let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3693 self.bring_to_top(&args[1], b_is_last);
3694
3695 self.sm.pop();
3696 self.sm.pop();
3697
3698 self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
3699 self.emit_op(StackOp::Opcode("OP_0NOTEQUAL".to_string()));
3700 self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
3701 self.emit_op(StackOp::Opcode("OP_MOD".to_string()));
3702
3703 self.sm.push(binding_name);
3704 self.track_depth();
3705 }
3706
3707 fn lower_clamp(
3710 &mut self,
3711 binding_name: &str,
3712 args: &[String],
3713 binding_index: usize,
3714 last_uses: &HashMap<String, usize>,
3715 ) {
3716 assert!(args.len() >= 3, "clamp requires 3 arguments");
3717
3718 let val_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3719 self.bring_to_top(&args[0], val_is_last);
3720
3721 let lo_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3722 self.bring_to_top(&args[1], lo_is_last);
3723
3724 self.sm.pop();
3725 self.sm.pop();
3726 self.emit_op(StackOp::Opcode("OP_MAX".to_string()));
3727 self.sm.push(""); let hi_is_last = self.is_last_use(&args[2], binding_index, last_uses);
3730 self.bring_to_top(&args[2], hi_is_last);
3731
3732 self.sm.pop();
3733 self.sm.pop();
3734 self.emit_op(StackOp::Opcode("OP_MIN".to_string()));
3735
3736 self.sm.push(binding_name);
3737 self.track_depth();
3738 }
3739
3740 fn lower_pow(
3745 &mut self,
3746 binding_name: &str,
3747 args: &[String],
3748 binding_index: usize,
3749 last_uses: &HashMap<String, usize>,
3750 ) {
3751 assert!(args.len() >= 2, "pow requires 2 arguments");
3752
3753 let base_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3754 self.bring_to_top(&args[0], base_is_last);
3755
3756 let exp_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3757 self.bring_to_top(&args[1], exp_is_last);
3758
3759 self.sm.pop();
3760 self.sm.pop();
3761
3762 self.emit_op(StackOp::Swap); self.emit_op(StackOp::Push(PushValue::Int(1))); const MAX_POW_ITERATIONS: i128 = 32;
3771 self.emit_op(StackOp::Push(PushValue::Int(2)));
3772 self.emit_op(StackOp::Opcode("OP_PICK".to_string())); self.emit_op(StackOp::Push(PushValue::Int(MAX_POW_ITERATIONS)));
3774 self.emit_op(StackOp::Opcode("OP_LESSTHANOREQUAL".to_string())); self.emit_op(StackOp::Opcode("OP_VERIFY".to_string()));
3776
3777 for i in 0..MAX_POW_ITERATIONS {
3778 self.emit_op(StackOp::Push(PushValue::Int(2)));
3780 self.emit_op(StackOp::Opcode("OP_PICK".to_string())); self.emit_op(StackOp::Push(PushValue::Int(i)));
3782 self.emit_op(StackOp::Opcode("OP_GREATERTHAN".to_string())); self.emit_op(StackOp::If {
3784 then_ops: vec![
3785 StackOp::Over, StackOp::Opcode("OP_MUL".to_string()), ],
3788 else_ops: vec![],
3789 });
3790 }
3791 self.emit_op(StackOp::Nip); self.emit_op(StackOp::Nip); self.sm.push(binding_name);
3796 self.track_depth();
3797 }
3798
3799 fn lower_mul_div(
3802 &mut self,
3803 binding_name: &str,
3804 args: &[String],
3805 binding_index: usize,
3806 last_uses: &HashMap<String, usize>,
3807 ) {
3808 assert!(args.len() >= 3, "mulDiv requires 3 arguments");
3809
3810 let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3811 self.bring_to_top(&args[0], a_is_last);
3812
3813 let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3814 self.bring_to_top(&args[1], b_is_last);
3815
3816 self.sm.pop();
3817 self.sm.pop();
3818 self.emit_op(StackOp::Opcode("OP_MUL".to_string()));
3819 self.sm.push(""); let c_is_last = self.is_last_use(&args[2], binding_index, last_uses);
3822 self.bring_to_top(&args[2], c_is_last);
3823
3824 self.sm.pop();
3825 self.sm.pop();
3826 self.emit_op(StackOp::Opcode("OP_DIV".to_string()));
3827
3828 self.sm.push(binding_name);
3829 self.track_depth();
3830 }
3831
3832 fn lower_percent_of(
3835 &mut self,
3836 binding_name: &str,
3837 args: &[String],
3838 binding_index: usize,
3839 last_uses: &HashMap<String, usize>,
3840 ) {
3841 assert!(args.len() >= 2, "percentOf requires 2 arguments");
3842
3843 let amount_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3844 self.bring_to_top(&args[0], amount_is_last);
3845
3846 let bps_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3847 self.bring_to_top(&args[1], bps_is_last);
3848
3849 self.sm.pop();
3850 self.sm.pop();
3851
3852 self.emit_op(StackOp::Opcode("OP_MUL".to_string()));
3853 self.emit_op(StackOp::Push(PushValue::Int(10000)));
3854 self.emit_op(StackOp::Opcode("OP_DIV".to_string()));
3855
3856 self.sm.push(binding_name);
3857 self.track_depth();
3858 }
3859
3860 fn lower_sqrt(
3864 &mut self,
3865 binding_name: &str,
3866 args: &[String],
3867 binding_index: usize,
3868 last_uses: &HashMap<String, usize>,
3869 ) {
3870 assert!(!args.is_empty(), "sqrt requires 1 argument");
3871
3872 let n_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3873 self.bring_to_top(&args[0], n_is_last);
3874
3875 self.sm.pop();
3876
3877 self.emit_op(StackOp::Opcode("OP_DUP".to_string()));
3880 let mut newton_ops = Vec::new();
3884 newton_ops.push(StackOp::Opcode("OP_DUP".to_string()));
3887 for _ in 0..16 {
3891 newton_ops.push(StackOp::Over); newton_ops.push(StackOp::Over); newton_ops.push(StackOp::Opcode("OP_DIV".to_string())); newton_ops.push(StackOp::Opcode("OP_ADD".to_string())); newton_ops.push(StackOp::Push(PushValue::Int(2))); newton_ops.push(StackOp::Opcode("OP_DIV".to_string())); }
3899
3900 newton_ops.push(StackOp::Opcode("OP_NIP".to_string()));
3903
3904 self.emit_op(StackOp::If {
3905 then_ops: newton_ops,
3906 else_ops: vec![], });
3908
3909 self.sm.push(binding_name);
3910 self.track_depth();
3911 }
3912
3913 fn lower_gcd(
3916 &mut self,
3917 binding_name: &str,
3918 args: &[String],
3919 binding_index: usize,
3920 last_uses: &HashMap<String, usize>,
3921 ) {
3922 assert!(args.len() >= 2, "gcd requires 2 arguments");
3923
3924 let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3925 self.bring_to_top(&args[0], a_is_last);
3926
3927 let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3928 self.bring_to_top(&args[1], b_is_last);
3929
3930 self.sm.pop();
3931 self.sm.pop();
3932
3933 self.emit_op(StackOp::Opcode("OP_ABS".to_string()));
3936 self.emit_op(StackOp::Swap);
3937 self.emit_op(StackOp::Opcode("OP_ABS".to_string()));
3938 self.emit_op(StackOp::Swap);
3939 for _ in 0..256 {
3943 self.emit_op(StackOp::Opcode("OP_DUP".to_string())); self.emit_op(StackOp::Opcode("OP_0NOTEQUAL".to_string())); self.emit_op(StackOp::If {
3949 then_ops: vec![
3950 StackOp::Opcode("OP_TUCK".to_string()), StackOp::Opcode("OP_MOD".to_string()), ],
3955 else_ops: vec![
3956 ],
3958 });
3959 }
3960
3961 self.emit_op(StackOp::Drop);
3964
3965 self.sm.push(binding_name);
3966 self.track_depth();
3967 }
3968
3969 fn lower_divmod(
3972 &mut self,
3973 binding_name: &str,
3974 args: &[String],
3975 binding_index: usize,
3976 last_uses: &HashMap<String, usize>,
3977 ) {
3978 assert!(args.len() >= 2, "divmod requires 2 arguments");
3979
3980 let a_is_last = self.is_last_use(&args[0], binding_index, last_uses);
3981 self.bring_to_top(&args[0], a_is_last);
3982
3983 let b_is_last = self.is_last_use(&args[1], binding_index, last_uses);
3984 self.bring_to_top(&args[1], b_is_last);
3985
3986 self.sm.pop();
3987 self.sm.pop();
3988
3989 self.emit_op(StackOp::Opcode("OP_2DUP".to_string())); self.emit_op(StackOp::Opcode("OP_DIV".to_string())); self.emit_op(StackOp::Opcode("OP_ROT".to_string())); self.emit_op(StackOp::Opcode("OP_ROT".to_string())); self.emit_op(StackOp::Opcode("OP_MOD".to_string())); self.emit_op(StackOp::Drop);
3999
4000 self.sm.push(binding_name);
4001 self.track_depth();
4002 }
4003
4004 fn lower_log2(
4014 &mut self,
4015 binding_name: &str,
4016 args: &[String],
4017 binding_index: usize,
4018 last_uses: &HashMap<String, usize>,
4019 ) {
4020 assert!(!args.is_empty(), "log2 requires 1 argument");
4021
4022 let n_is_last = self.is_last_use(&args[0], binding_index, last_uses);
4023 self.bring_to_top(&args[0], n_is_last);
4024
4025 self.sm.pop();
4026
4027 self.emit_op(StackOp::Push(PushValue::Int(0))); const LOG2_ITERATIONS: usize = 64;
4033 for _ in 0..LOG2_ITERATIONS {
4034 self.emit_op(StackOp::Swap); self.emit_op(StackOp::Opcode("OP_DUP".to_string())); self.emit_op(StackOp::Push(PushValue::Int(1))); self.emit_op(StackOp::Opcode("OP_GREATERTHAN".to_string())); self.emit_op(StackOp::If {
4040 then_ops: vec![
4041 StackOp::Push(PushValue::Int(2)), StackOp::Opcode("OP_DIV".to_string()), StackOp::Swap, StackOp::Opcode("OP_1ADD".to_string()), StackOp::Swap, ],
4047 else_ops: vec![],
4048 });
4049 self.emit_op(StackOp::Swap); }
4053 self.emit_op(StackOp::Nip); self.sm.push(binding_name);
4058 self.track_depth();
4059 }
4060}
4061
4062pub fn lower_to_stack(program: &ANFProgram) -> Result<Vec<StackMethod>, String> {
4070 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4074 lower_to_stack_inner(program)
4075 }))
4076 .unwrap_or_else(|e| {
4077 if let Some(s) = e.downcast_ref::<String>() {
4078 Err(format!("stack lowering: {}", s))
4079 } else if let Some(s) = e.downcast_ref::<&str>() {
4080 Err(format!("stack lowering: {}", s))
4081 } else {
4082 Err("stack lowering: internal error".to_string())
4083 }
4084 })
4085}
4086
4087fn lower_to_stack_inner(program: &ANFProgram) -> Result<Vec<StackMethod>, String> {
4088 let mut private_methods: HashMap<String, ANFMethod> = HashMap::new();
4090 for method in &program.methods {
4091 if !method.is_public && method.name != "constructor" {
4092 private_methods.insert(method.name.clone(), method.clone());
4093 }
4094 }
4095
4096 let mut methods = Vec::new();
4097
4098 for method in &program.methods {
4099 if method.name == "constructor" || (!method.is_public && method.name != "constructor") {
4101 continue;
4102 }
4103 let sm = lower_method_with_private_methods(method, &program.properties, &private_methods)?;
4104 methods.push(sm);
4105 }
4106
4107 Ok(methods)
4108}
4109
4110fn method_uses_check_preimage(bindings: &[ANFBinding]) -> bool {
4114 bindings.iter().any(|b| matches!(&b.value, ANFValue::CheckPreimage { .. }))
4115}
4116
4117fn method_uses_code_part(bindings: &[ANFBinding]) -> bool {
4121 bindings.iter().any(|b| match &b.value {
4122 ANFValue::AddOutput { .. } | ANFValue::AddRawOutput { .. } => true,
4123 ANFValue::Call { func, .. } if func == "computeStateOutput" || func == "computeStateOutputHash" => true,
4124 ANFValue::If { then, else_branch, .. } => method_uses_code_part(then) || method_uses_code_part(else_branch),
4125 ANFValue::Loop { body, .. } => method_uses_code_part(body),
4126 _ => false,
4127 })
4128}
4129
4130fn lower_method_with_private_methods(
4131 method: &ANFMethod,
4132 properties: &[ANFProperty],
4133 private_methods: &HashMap<String, ANFMethod>,
4134) -> Result<StackMethod, String> {
4135 let mut param_names: Vec<String> = method.params.iter().map(|p| p.name.clone()).collect();
4136
4137 if method_uses_check_preimage(&method.body) {
4143 param_names.insert(0, "_opPushTxSig".to_string());
4144 if method_uses_code_part(&method.body) {
4148 param_names.insert(0, "_codePart".to_string());
4149 }
4150 }
4151
4152 let mut ctx = LoweringContext::new(¶m_names, properties);
4153 ctx.private_methods = private_methods.clone();
4154 ctx.lower_bindings(&method.body, method.is_public);
4157
4158 let has_deserialize_state = method.body.iter().any(|b| matches!(&b.value, ANFValue::DeserializeState { .. }));
4160 if method.is_public && has_deserialize_state && ctx.sm.depth() > 1 {
4161 let excess = ctx.sm.depth() - 1;
4162 for _ in 0..excess {
4163 ctx.emit_op(StackOp::Nip);
4164 ctx.sm.remove_at_depth(1);
4165 }
4166 }
4167
4168 if ctx.max_depth > MAX_STACK_DEPTH {
4169 return Err(format!(
4170 "method '{}' exceeds maximum stack depth of {} (actual: {}). Simplify the contract logic.",
4171 method.name, MAX_STACK_DEPTH, ctx.max_depth
4172 ));
4173 }
4174
4175 Ok(StackMethod {
4176 name: method.name.clone(),
4177 source_locs: ctx.source_locs,
4178 ops: ctx.ops,
4179 max_stack_depth: ctx.max_depth,
4180 })
4181}
4182
4183fn hex_to_bytes(hex_str: &str) -> Vec<u8> {
4188 if hex_str.is_empty() {
4189 return Vec::new();
4190 }
4191 assert!(
4192 hex_str.len() % 2 == 0,
4193 "invalid hex string length: {}",
4194 hex_str.len()
4195 );
4196 (0..hex_str.len())
4197 .step_by(2)
4198 .map(|i| u8::from_str_radix(&hex_str[i..i + 2], 16).unwrap_or(0))
4199 .collect()
4200}
4201
4202#[cfg(test)]
4207mod tests {
4208 use super::*;
4209 use crate::ir::{ANFBinding, ANFMethod, ANFParam, ANFProgram, ANFProperty, ANFValue};
4210
4211 fn p2pkh_program() -> ANFProgram {
4213 ANFProgram {
4214 contract_name: "P2PKH".to_string(),
4215 properties: vec![ANFProperty {
4216 name: "pubKeyHash".to_string(),
4217 prop_type: "Addr".to_string(),
4218 readonly: true,
4219 initial_value: None,
4220 }],
4221 methods: vec![ANFMethod {
4222 name: "unlock".to_string(),
4223 params: vec![
4224 ANFParam {
4225 name: "sig".to_string(),
4226 param_type: "Sig".to_string(),
4227 },
4228 ANFParam {
4229 name: "pubKey".to_string(),
4230 param_type: "PubKey".to_string(),
4231 },
4232 ],
4233 body: vec![
4234 ANFBinding {
4235 name: "t0".to_string(),
4236 value: ANFValue::LoadParam {
4237 name: "sig".to_string(),
4238 },
4239 source_loc: None,
4240 },
4241 ANFBinding {
4242 name: "t1".to_string(),
4243 value: ANFValue::LoadParam {
4244 name: "pubKey".to_string(),
4245 },
4246 source_loc: None,
4247 },
4248 ANFBinding {
4249 name: "t2".to_string(),
4250 value: ANFValue::LoadProp {
4251 name: "pubKeyHash".to_string(),
4252 },
4253 source_loc: None,
4254 },
4255 ANFBinding {
4256 name: "t3".to_string(),
4257 value: ANFValue::Call {
4258 func: "hash160".to_string(),
4259 args: vec!["t1".to_string()],
4260 },
4261 source_loc: None,
4262 },
4263 ANFBinding {
4264 name: "t4".to_string(),
4265 value: ANFValue::BinOp {
4266 op: "===".to_string(),
4267 left: "t3".to_string(),
4268 right: "t2".to_string(),
4269 result_type: None,
4270 },
4271 source_loc: None,
4272 },
4273 ANFBinding {
4274 name: "t5".to_string(),
4275 value: ANFValue::Assert {
4276 value: "t4".to_string(),
4277 },
4278 source_loc: None,
4279 },
4280 ANFBinding {
4281 name: "t6".to_string(),
4282 value: ANFValue::Call {
4283 func: "checkSig".to_string(),
4284 args: vec!["t0".to_string(), "t1".to_string()],
4285 },
4286 source_loc: None,
4287 },
4288 ANFBinding {
4289 name: "t7".to_string(),
4290 value: ANFValue::Assert {
4291 value: "t6".to_string(),
4292 },
4293 source_loc: None,
4294 },
4295 ],
4296 is_public: true,
4297 }],
4298 }
4299 }
4300
4301 #[test]
4302 fn test_p2pkh_stack_lowering_produces_placeholder_ops() {
4303 let program = p2pkh_program();
4304 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4305 assert_eq!(methods.len(), 1);
4306 assert_eq!(methods[0].name, "unlock");
4307
4308 let has_placeholder = methods[0].ops.iter().any(|op| {
4310 matches!(op, StackOp::Placeholder { .. })
4311 });
4312 assert!(
4313 has_placeholder,
4314 "P2PKH should have Placeholder ops for constructor params, ops: {:?}",
4315 methods[0].ops
4316 );
4317 }
4318
4319 #[test]
4320 fn test_placeholder_has_correct_param_index() {
4321 let program = p2pkh_program();
4322 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4323
4324 let placeholders: Vec<&StackOp> = methods[0]
4326 .ops
4327 .iter()
4328 .filter(|op| matches!(op, StackOp::Placeholder { .. }))
4329 .collect();
4330
4331 assert!(
4332 !placeholders.is_empty(),
4333 "should have at least one Placeholder"
4334 );
4335
4336 if let StackOp::Placeholder {
4338 param_index,
4339 param_name,
4340 } = placeholders[0]
4341 {
4342 assert_eq!(*param_index, 0);
4343 assert_eq!(param_name, "pubKeyHash");
4344 } else {
4345 panic!("expected Placeholder op");
4346 }
4347 }
4348
4349 #[test]
4350 fn test_with_initial_values_no_placeholder_ops() {
4351 let mut program = p2pkh_program();
4352 program.properties[0].initial_value =
4354 Some(serde_json::Value::String("aabbccdd".to_string()));
4355
4356 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4357 let has_placeholder = methods[0].ops.iter().any(|op| {
4358 matches!(op, StackOp::Placeholder { .. })
4359 });
4360 assert!(
4361 !has_placeholder,
4362 "with initial values, there should be no Placeholder ops"
4363 );
4364 }
4365
4366 #[test]
4367 fn test_stack_lowering_produces_standard_opcodes() {
4368 let program = p2pkh_program();
4369 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4370
4371 let opcodes: Vec<&str> = methods[0]
4373 .ops
4374 .iter()
4375 .filter_map(|op| match op {
4376 StackOp::Opcode(code) => Some(code.as_str()),
4377 _ => None,
4378 })
4379 .collect();
4380
4381 assert!(
4383 opcodes.contains(&"OP_HASH160"),
4384 "expected OP_HASH160 in opcodes: {:?}",
4385 opcodes
4386 );
4387 assert!(
4388 opcodes.contains(&"OP_CHECKSIG"),
4389 "expected OP_CHECKSIG in opcodes: {:?}",
4390 opcodes
4391 );
4392 }
4393
4394 #[test]
4395 fn test_max_stack_depth_is_tracked() {
4396 let program = p2pkh_program();
4397 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4398 assert!(
4399 methods[0].max_stack_depth > 0,
4400 "max_stack_depth should be > 0"
4401 );
4402 assert!(
4404 methods[0].max_stack_depth <= 10,
4405 "max_stack_depth should be reasonable for P2PKH, got: {}",
4406 methods[0].max_stack_depth
4407 );
4408 }
4409
4410 fn collect_all_opcodes(ops: &[StackOp]) -> Vec<String> {
4415 let mut result = Vec::new();
4416 for op in ops {
4417 match op {
4418 StackOp::Opcode(code) => result.push(code.clone()),
4419 StackOp::If { then_ops, else_ops } => {
4420 result.push("OP_IF".to_string());
4421 result.extend(collect_all_opcodes(then_ops));
4422 result.push("OP_ELSE".to_string());
4423 result.extend(collect_all_opcodes(else_ops));
4424 result.push("OP_ENDIF".to_string());
4425 }
4426 StackOp::Push(PushValue::Int(n)) => {
4427 result.push(format!("PUSH({})", n));
4428 }
4429 StackOp::Drop => result.push("OP_DROP".to_string()),
4430 StackOp::Swap => result.push("OP_SWAP".to_string()),
4431 StackOp::Dup => result.push("OP_DUP".to_string()),
4432 StackOp::Over => result.push("OP_OVER".to_string()),
4433 StackOp::Rot => result.push("OP_ROT".to_string()),
4434 StackOp::Nip => result.push("OP_NIP".to_string()),
4435 _ => {}
4436 }
4437 }
4438 result
4439 }
4440
4441 fn collect_opcodes_in_if_branches(ops: &[StackOp]) -> (Vec<String>, Vec<String>) {
4442 for op in ops {
4443 if let StackOp::If { then_ops, else_ops } = op {
4444 return (collect_all_opcodes(then_ops), collect_all_opcodes(else_ops));
4445 }
4446 }
4447 (vec![], vec![])
4448 }
4449
4450 #[test]
4455 fn test_extract_output_hash_uses_offset_40() {
4456 let program = ANFProgram {
4458 contract_name: "TestExtract".to_string(),
4459 properties: vec![ANFProperty {
4460 name: "val".to_string(),
4461 prop_type: "bigint".to_string(),
4462 readonly: false,
4463 initial_value: Some(serde_json::Value::Number(serde_json::Number::from(0))),
4464 }],
4465 methods: vec![ANFMethod {
4466 name: "check".to_string(),
4467 params: vec![
4468 ANFParam { name: "preimage".to_string(), param_type: "SigHashPreimage".to_string() },
4469 ],
4470 body: vec![
4471 ANFBinding {
4472 name: "t0".to_string(),
4473 value: ANFValue::LoadParam { name: "preimage".to_string() },
4474 source_loc: None,
4475 },
4476 ANFBinding {
4477 name: "t1".to_string(),
4478 value: ANFValue::Call {
4479 func: "extractOutputHash".to_string(),
4480 args: vec!["t0".to_string()],
4481 },
4482 source_loc: None,
4483 },
4484 ANFBinding {
4485 name: "t2".to_string(),
4486 value: ANFValue::LoadConst { value: serde_json::Value::Bool(true) },
4487 source_loc: None,
4488 },
4489 ANFBinding {
4490 name: "t3".to_string(),
4491 value: ANFValue::Assert { value: "t2".to_string() },
4492 source_loc: None,
4493 },
4494 ],
4495 is_public: true,
4496 }],
4497 };
4498
4499 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4500 let opcodes = collect_all_opcodes(&methods[0].ops);
4501
4502 assert!(
4504 opcodes.contains(&"PUSH(40)".to_string()),
4505 "extractOutputHash should use offset 40 (BIP-143 hashOutputs starts at size-40), ops: {:?}",
4506 opcodes
4507 );
4508 assert!(
4509 !opcodes.contains(&"PUSH(44)".to_string()),
4510 "extractOutputHash should NOT use offset 44, ops: {:?}",
4511 opcodes
4512 );
4513 }
4514
4515 #[test]
4520 fn test_terminal_if_propagates_terminal_assert() {
4521 let program = ANFProgram {
4524 contract_name: "TerminalIf".to_string(),
4525 properties: vec![],
4526 methods: vec![ANFMethod {
4527 name: "check".to_string(),
4528 params: vec![
4529 ANFParam { name: "mode".to_string(), param_type: "boolean".to_string() },
4530 ANFParam { name: "x".to_string(), param_type: "bigint".to_string() },
4531 ],
4532 body: vec![
4533 ANFBinding {
4534 name: "t0".to_string(),
4535 value: ANFValue::LoadParam { name: "mode".to_string() },
4536 source_loc: None,
4537 },
4538 ANFBinding {
4539 name: "t1".to_string(),
4540 value: ANFValue::LoadParam { name: "x".to_string() },
4541 source_loc: None,
4542 },
4543 ANFBinding {
4544 name: "t2".to_string(),
4545 value: ANFValue::If {
4546 cond: "t0".to_string(),
4547 then: vec![
4548 ANFBinding {
4549 name: "t3".to_string(),
4550 value: ANFValue::LoadConst {
4551 value: serde_json::Value::Number(serde_json::Number::from(10)),
4552 },
4553 source_loc: None,
4554 },
4555 ANFBinding {
4556 name: "t4".to_string(),
4557 value: ANFValue::BinOp {
4558 op: ">".to_string(),
4559 left: "t1".to_string(),
4560 right: "t3".to_string(),
4561 result_type: None,
4562 },
4563 source_loc: None,
4564 },
4565 ANFBinding {
4566 name: "t5".to_string(),
4567 value: ANFValue::Assert { value: "t4".to_string() },
4568 source_loc: None,
4569 },
4570 ],
4571 else_branch: vec![
4572 ANFBinding {
4573 name: "t6".to_string(),
4574 value: ANFValue::LoadConst {
4575 value: serde_json::Value::Number(serde_json::Number::from(5)),
4576 },
4577 source_loc: None,
4578 },
4579 ANFBinding {
4580 name: "t7".to_string(),
4581 value: ANFValue::BinOp {
4582 op: ">".to_string(),
4583 left: "t1".to_string(),
4584 right: "t6".to_string(),
4585 result_type: None,
4586 },
4587 source_loc: None,
4588 },
4589 ANFBinding {
4590 name: "t8".to_string(),
4591 value: ANFValue::Assert { value: "t7".to_string() },
4592 source_loc: None,
4593 },
4594 ],
4595 },
4596 source_loc: None,
4597 },
4598 ],
4599 is_public: true,
4600 }],
4601 };
4602
4603 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4604
4605 let (then_opcodes, else_opcodes) = collect_opcodes_in_if_branches(&methods[0].ops);
4607
4608 assert!(
4610 !then_opcodes.contains(&"OP_VERIFY".to_string()),
4611 "then branch should not contain OP_VERIFY (terminal assert), got: {:?}",
4612 then_opcodes
4613 );
4614 assert!(
4615 !else_opcodes.contains(&"OP_VERIFY".to_string()),
4616 "else branch should not contain OP_VERIFY (terminal assert), got: {:?}",
4617 else_opcodes
4618 );
4619 }
4620
4621 #[test]
4626 fn test_unpack_emits_bin2num() {
4627 let program = ANFProgram {
4628 contract_name: "TestUnpack".to_string(),
4629 properties: vec![],
4630 methods: vec![ANFMethod {
4631 name: "check".to_string(),
4632 params: vec![
4633 ANFParam { name: "data".to_string(), param_type: "ByteString".to_string() },
4634 ],
4635 body: vec![
4636 ANFBinding {
4637 name: "t0".to_string(),
4638 value: ANFValue::LoadParam { name: "data".to_string() },
4639 source_loc: None,
4640 },
4641 ANFBinding {
4642 name: "t1".to_string(),
4643 value: ANFValue::Call {
4644 func: "unpack".to_string(),
4645 args: vec!["t0".to_string()],
4646 },
4647 source_loc: None,
4648 },
4649 ANFBinding {
4650 name: "t2".to_string(),
4651 value: ANFValue::LoadConst {
4652 value: serde_json::Value::Number(serde_json::Number::from(42)),
4653 },
4654 source_loc: None,
4655 },
4656 ANFBinding {
4657 name: "t3".to_string(),
4658 value: ANFValue::BinOp {
4659 op: "===".to_string(),
4660 left: "t1".to_string(),
4661 right: "t2".to_string(),
4662 result_type: None,
4663 },
4664 source_loc: None,
4665 },
4666 ANFBinding {
4667 name: "t4".to_string(),
4668 value: ANFValue::Assert { value: "t3".to_string() },
4669 source_loc: None,
4670 },
4671 ],
4672 is_public: true,
4673 }],
4674 };
4675
4676 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4677 let opcodes = collect_all_opcodes(&methods[0].ops);
4678 assert!(
4679 opcodes.contains(&"OP_BIN2NUM".to_string()),
4680 "unpack should emit OP_BIN2NUM, got: {:?}",
4681 opcodes
4682 );
4683 }
4684
4685 #[test]
4686 fn test_pack_is_noop() {
4687 let program = ANFProgram {
4688 contract_name: "TestPack".to_string(),
4689 properties: vec![],
4690 methods: vec![ANFMethod {
4691 name: "check".to_string(),
4692 params: vec![
4693 ANFParam { name: "x".to_string(), param_type: "bigint".to_string() },
4694 ],
4695 body: vec![
4696 ANFBinding {
4697 name: "t0".to_string(),
4698 value: ANFValue::LoadParam { name: "x".to_string() },
4699 source_loc: None,
4700 },
4701 ANFBinding {
4702 name: "t1".to_string(),
4703 value: ANFValue::Call {
4704 func: "pack".to_string(),
4705 args: vec!["t0".to_string()],
4706 },
4707 source_loc: None,
4708 },
4709 ANFBinding {
4710 name: "t2".to_string(),
4711 value: ANFValue::LoadConst {
4712 value: serde_json::Value::Bool(true),
4713 },
4714 source_loc: None,
4715 },
4716 ANFBinding {
4717 name: "t3".to_string(),
4718 value: ANFValue::Assert { value: "t2".to_string() },
4719 source_loc: None,
4720 },
4721 ],
4722 is_public: true,
4723 }],
4724 };
4725
4726 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4727 let opcodes = collect_all_opcodes(&methods[0].ops);
4728 assert!(
4730 !opcodes.contains(&"OP_BIN2NUM".to_string()),
4731 "pack should not emit OP_BIN2NUM, got: {:?}",
4732 opcodes
4733 );
4734 assert!(
4735 !opcodes.contains(&"OP_NUM2BIN".to_string()),
4736 "pack should not emit OP_NUM2BIN, got: {:?}",
4737 opcodes
4738 );
4739 }
4740
4741 #[test]
4742 fn test_to_byte_string_is_noop() {
4743 let program = ANFProgram {
4744 contract_name: "TestToByteString".to_string(),
4745 properties: vec![],
4746 methods: vec![ANFMethod {
4747 name: "check".to_string(),
4748 params: vec![
4749 ANFParam { name: "x".to_string(), param_type: "bigint".to_string() },
4750 ],
4751 body: vec![
4752 ANFBinding {
4753 name: "t0".to_string(),
4754 value: ANFValue::LoadParam { name: "x".to_string() },
4755 source_loc: None,
4756 },
4757 ANFBinding {
4758 name: "t1".to_string(),
4759 value: ANFValue::Call {
4760 func: "toByteString".to_string(),
4761 args: vec!["t0".to_string()],
4762 },
4763 source_loc: None,
4764 },
4765 ANFBinding {
4766 name: "t2".to_string(),
4767 value: ANFValue::LoadConst {
4768 value: serde_json::Value::Bool(true),
4769 },
4770 source_loc: None,
4771 },
4772 ANFBinding {
4773 name: "t3".to_string(),
4774 value: ANFValue::Assert { value: "t2".to_string() },
4775 source_loc: None,
4776 },
4777 ],
4778 is_public: true,
4779 }],
4780 };
4781
4782 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4783 let opcodes = collect_all_opcodes(&methods[0].ops);
4784 assert!(
4786 !opcodes.contains(&"OP_BIN2NUM".to_string()),
4787 "toByteString should not emit OP_BIN2NUM, got: {:?}",
4788 opcodes
4789 );
4790 }
4791
4792 #[test]
4797 fn test_sqrt_has_zero_guard() {
4798 let program = ANFProgram {
4799 contract_name: "TestSqrt".to_string(),
4800 properties: vec![],
4801 methods: vec![ANFMethod {
4802 name: "check".to_string(),
4803 params: vec![
4804 ANFParam { name: "n".to_string(), param_type: "bigint".to_string() },
4805 ],
4806 body: vec![
4807 ANFBinding {
4808 name: "t0".to_string(),
4809 value: ANFValue::LoadParam { name: "n".to_string() },
4810 source_loc: None,
4811 },
4812 ANFBinding {
4813 name: "t1".to_string(),
4814 value: ANFValue::Call {
4815 func: "sqrt".to_string(),
4816 args: vec!["t0".to_string()],
4817 },
4818 source_loc: None,
4819 },
4820 ANFBinding {
4821 name: "t2".to_string(),
4822 value: ANFValue::LoadConst {
4823 value: serde_json::Value::Number(serde_json::Number::from(0)),
4824 },
4825 source_loc: None,
4826 },
4827 ANFBinding {
4828 name: "t3".to_string(),
4829 value: ANFValue::BinOp {
4830 op: ">=".to_string(),
4831 left: "t1".to_string(),
4832 right: "t2".to_string(),
4833 result_type: None,
4834 },
4835 source_loc: None,
4836 },
4837 ANFBinding {
4838 name: "t4".to_string(),
4839 value: ANFValue::Assert { value: "t3".to_string() },
4840 source_loc: None,
4841 },
4842 ],
4843 is_public: true,
4844 }],
4845 };
4846
4847 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4848 let opcodes = collect_all_opcodes(&methods[0].ops);
4849
4850 let dup_idx = opcodes.iter().position(|o| o == "OP_DUP");
4853 let if_idx = opcodes.iter().position(|o| o == "OP_IF");
4854
4855 assert!(
4856 dup_idx.is_some() && if_idx.is_some(),
4857 "sqrt should have OP_DUP and OP_IF for zero guard, got: {:?}",
4858 opcodes
4859 );
4860 assert!(
4861 dup_idx.unwrap() < if_idx.unwrap(),
4862 "OP_DUP should come before OP_IF in sqrt zero guard, got: {:?}",
4863 opcodes
4864 );
4865 }
4866
4867 #[test]
4872 fn test_loop_cleans_up_unused_iter_var() {
4873 let program = ANFProgram {
4877 contract_name: "TestLoopCleanup".to_string(),
4878 properties: vec![],
4879 methods: vec![ANFMethod {
4880 name: "check".to_string(),
4881 params: vec![
4882 ANFParam { name: "x".to_string(), param_type: "bigint".to_string() },
4883 ],
4884 body: vec![
4885 ANFBinding {
4886 name: "t0".to_string(),
4887 value: ANFValue::LoadParam { name: "x".to_string() },
4888 source_loc: None,
4889 },
4890 ANFBinding {
4891 name: "t_loop".to_string(),
4892 value: ANFValue::Loop {
4893 count: 3,
4894 body: vec![
4895 ANFBinding {
4897 name: "t1".to_string(),
4898 value: ANFValue::LoadParam { name: "x".to_string() },
4899 source_loc: None,
4900 },
4901 ANFBinding {
4902 name: "t2".to_string(),
4903 value: ANFValue::Assert { value: "t1".to_string() },
4904 source_loc: None,
4905 },
4906 ],
4907 iter_var: "__i".to_string(),
4908 },
4909 source_loc: None,
4910 },
4911 ANFBinding {
4912 name: "t_final".to_string(),
4913 value: ANFValue::LoadConst {
4914 value: serde_json::Value::Bool(true),
4915 },
4916 source_loc: None,
4917 },
4918 ANFBinding {
4919 name: "t_assert".to_string(),
4920 value: ANFValue::Assert { value: "t_final".to_string() },
4921 source_loc: None,
4922 },
4923 ],
4924 is_public: true,
4925 }],
4926 };
4927
4928 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
4929 let opcodes = collect_all_opcodes(&methods[0].ops);
4930
4931 let drop_count = opcodes.iter().filter(|o| o.as_str() == "OP_DROP").count();
4935 assert!(
4936 drop_count >= 3,
4937 "unused iter var should be dropped after each iteration; expected >= 3 OP_DROPs, got {}: {:?}",
4938 drop_count,
4939 opcodes
4940 );
4941 }
4942
4943 #[test]
4948 fn test_push_value_int_large_values() {
4949 let large_val: i128 = (i64::MAX as i128) + 1;
4951 let push = PushValue::Int(large_val);
4952 if let PushValue::Int(v) = push {
4953 assert_eq!(v, large_val, "PushValue::Int should store values > i64::MAX without truncation");
4954 } else {
4955 panic!("expected PushValue::Int");
4956 }
4957
4958 let neg_val: i128 = (i64::MIN as i128) - 1;
4960 let push_neg = PushValue::Int(neg_val);
4961 if let PushValue::Int(v) = push_neg {
4962 assert_eq!(v, neg_val, "PushValue::Int should store values < i64::MIN without truncation");
4963 } else {
4964 panic!("expected PushValue::Int");
4965 }
4966 }
4967
4968 #[test]
4969 fn test_push_value_int_encodes_large_number() {
4970 use crate::codegen::emit::encode_push_int;
4972
4973 let large_val: i128 = 1i128 << 100;
4974 let (hex, _asm) = encode_push_int(large_val);
4975 assert!(!hex.is_empty(), "encoding of 2^100 should produce non-empty hex");
4977
4978 assert!(
4982 hex.len() >= 26,
4983 "2^100 should need at least 13 bytes of push data, got hex length {}: {}",
4984 hex.len(),
4985 hex
4986 );
4987 }
4988
4989 #[test]
4994 fn test_log2_uses_bit_scanning_not_byte_approx() {
4995 let program = ANFProgram {
4996 contract_name: "TestLog2".to_string(),
4997 properties: vec![],
4998 methods: vec![ANFMethod {
4999 name: "check".to_string(),
5000 params: vec![
5001 ANFParam { name: "n".to_string(), param_type: "bigint".to_string() },
5002 ],
5003 body: vec![
5004 ANFBinding {
5005 name: "t0".to_string(),
5006 value: ANFValue::LoadParam { name: "n".to_string() },
5007 source_loc: None,
5008 },
5009 ANFBinding {
5010 name: "t1".to_string(),
5011 value: ANFValue::Call {
5012 func: "log2".to_string(),
5013 args: vec!["t0".to_string()],
5014 },
5015 source_loc: None,
5016 },
5017 ANFBinding {
5018 name: "t2".to_string(),
5019 value: ANFValue::LoadConst {
5020 value: serde_json::Value::Number(serde_json::Number::from(0)),
5021 },
5022 source_loc: None,
5023 },
5024 ANFBinding {
5025 name: "t3".to_string(),
5026 value: ANFValue::BinOp {
5027 op: ">=".to_string(),
5028 left: "t1".to_string(),
5029 right: "t2".to_string(),
5030 result_type: None,
5031 },
5032 source_loc: None,
5033 },
5034 ANFBinding {
5035 name: "t4".to_string(),
5036 value: ANFValue::Assert { value: "t3".to_string() },
5037 source_loc: None,
5038 },
5039 ],
5040 is_public: true,
5041 }],
5042 };
5043
5044 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5045 let opcodes = collect_all_opcodes(&methods[0].ops);
5046
5047 assert!(
5049 opcodes.contains(&"OP_DIV".to_string()),
5050 "log2 should use OP_DIV (bit-scanning), got: {:?}",
5051 opcodes
5052 );
5053 assert!(
5054 opcodes.contains(&"OP_GREATERTHAN".to_string()),
5055 "log2 should use OP_GREATERTHAN (bit-scanning), got: {:?}",
5056 opcodes
5057 );
5058
5059 assert!(
5061 !opcodes.contains(&"OP_SIZE".to_string()),
5062 "log2 should NOT use OP_SIZE (old byte approximation), got: {:?}",
5063 opcodes
5064 );
5065 assert!(
5066 !opcodes.contains(&"OP_MUL".to_string()),
5067 "log2 should NOT use OP_MUL (old byte approximation), got: {:?}",
5068 opcodes
5069 );
5070
5071 assert!(
5073 opcodes.contains(&"OP_1ADD".to_string()),
5074 "log2 should use OP_1ADD (counter increment), got: {:?}",
5075 opcodes
5076 );
5077 }
5078
5079 #[test]
5084 fn test_reverse_bytes_uses_split_cat_not_op_reverse() {
5085 let program = ANFProgram {
5086 contract_name: "TestReverse".to_string(),
5087 properties: vec![],
5088 methods: vec![ANFMethod {
5089 name: "check".to_string(),
5090 params: vec![
5091 ANFParam { name: "data".to_string(), param_type: "ByteString".to_string() },
5092 ],
5093 body: vec![
5094 ANFBinding {
5095 name: "t0".to_string(),
5096 value: ANFValue::LoadParam { name: "data".to_string() },
5097 source_loc: None,
5098 },
5099 ANFBinding {
5100 name: "t1".to_string(),
5101 value: ANFValue::Call {
5102 func: "reverseBytes".to_string(),
5103 args: vec!["t0".to_string()],
5104 },
5105 source_loc: None,
5106 },
5107 ANFBinding {
5108 name: "t2".to_string(),
5109 value: ANFValue::LoadConst {
5110 value: serde_json::Value::Bool(true),
5111 },
5112 source_loc: None,
5113 },
5114 ANFBinding {
5115 name: "t3".to_string(),
5116 value: ANFValue::Assert { value: "t2".to_string() },
5117 source_loc: None,
5118 },
5119 ],
5120 is_public: true,
5121 }],
5122 };
5123
5124 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5125 let opcodes = collect_all_opcodes(&methods[0].ops);
5126
5127 assert!(
5129 !opcodes.contains(&"OP_REVERSE".to_string()),
5130 "reverseBytes must NOT emit OP_REVERSE (does not exist), got: {:?}",
5131 opcodes
5132 );
5133
5134 assert!(
5136 opcodes.contains(&"OP_SPLIT".to_string()),
5137 "reverseBytes should emit OP_SPLIT for byte peeling, got: {:?}",
5138 opcodes
5139 );
5140 assert!(
5141 opcodes.contains(&"OP_CAT".to_string()),
5142 "reverseBytes should emit OP_CAT for reassembly, got: {:?}",
5143 opcodes
5144 );
5145
5146 assert!(
5148 opcodes.contains(&"OP_SIZE".to_string()),
5149 "reverseBytes should emit OP_SIZE for length check, got: {:?}",
5150 opcodes
5151 );
5152 }
5153
5154 #[test]
5159 fn test_method_count_matches_public_methods() {
5160 let program = p2pkh_program();
5162 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5163
5164 assert_eq!(
5166 methods.len(),
5167 1,
5168 "expected 1 stack method (unlock), got {}: {:?}",
5169 methods.len(),
5170 methods.iter().map(|m| &m.name).collect::<Vec<_>>()
5171 );
5172 assert_eq!(methods[0].name, "unlock");
5173 }
5174
5175 #[test]
5180 fn test_multi_method_dispatch() {
5181 let program = ANFProgram {
5182 contract_name: "Multi".to_string(),
5183 properties: vec![],
5184 methods: vec![
5185 ANFMethod {
5186 name: "constructor".to_string(),
5187 params: vec![],
5188 body: vec![],
5189 is_public: false,
5190 },
5191 ANFMethod {
5192 name: "method1".to_string(),
5193 params: vec![ANFParam {
5194 name: "x".to_string(),
5195 param_type: "bigint".to_string(),
5196 }],
5197 body: vec![
5198 ANFBinding {
5199 name: "t0".to_string(),
5200 value: ANFValue::LoadParam { name: "x".to_string() },
5201 source_loc: None,
5202 },
5203 ANFBinding {
5204 name: "t1".to_string(),
5205 value: ANFValue::LoadConst {
5206 value: serde_json::Value::Number(serde_json::Number::from(42)),
5207 },
5208 source_loc: None,
5209 },
5210 ANFBinding {
5211 name: "t2".to_string(),
5212 value: ANFValue::BinOp {
5213 op: "===".to_string(),
5214 left: "t0".to_string(),
5215 right: "t1".to_string(),
5216 result_type: None,
5217 },
5218 source_loc: None,
5219 },
5220 ANFBinding {
5221 name: "t3".to_string(),
5222 value: ANFValue::Assert { value: "t2".to_string() },
5223 source_loc: None,
5224 },
5225 ],
5226 is_public: true,
5227 },
5228 ANFMethod {
5229 name: "method2".to_string(),
5230 params: vec![ANFParam {
5231 name: "y".to_string(),
5232 param_type: "bigint".to_string(),
5233 }],
5234 body: vec![
5235 ANFBinding {
5236 name: "t0".to_string(),
5237 value: ANFValue::LoadParam { name: "y".to_string() },
5238 source_loc: None,
5239 },
5240 ANFBinding {
5241 name: "t1".to_string(),
5242 value: ANFValue::LoadConst {
5243 value: serde_json::Value::Number(serde_json::Number::from(100)),
5244 },
5245 source_loc: None,
5246 },
5247 ANFBinding {
5248 name: "t2".to_string(),
5249 value: ANFValue::BinOp {
5250 op: "===".to_string(),
5251 left: "t0".to_string(),
5252 right: "t1".to_string(),
5253 result_type: None,
5254 },
5255 source_loc: None,
5256 },
5257 ANFBinding {
5258 name: "t3".to_string(),
5259 value: ANFValue::Assert { value: "t2".to_string() },
5260 source_loc: None,
5261 },
5262 ],
5263 is_public: true,
5264 },
5265 ],
5266 };
5267
5268 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5269 assert_eq!(
5270 methods.len(),
5271 2,
5272 "expected 2 stack methods, got {}: {:?}",
5273 methods.len(),
5274 methods.iter().map(|m| &m.name).collect::<Vec<_>>()
5275 );
5276 }
5277
5278 #[test]
5283 fn test_extract_outputs_uses_offset_40() {
5284 let program = ANFProgram {
5285 contract_name: "OutputsCheck".to_string(),
5286 properties: vec![],
5287 methods: vec![ANFMethod {
5288 name: "check".to_string(),
5289 params: vec![ANFParam {
5290 name: "preimage".to_string(),
5291 param_type: "SigHashPreimage".to_string(),
5292 }],
5293 body: vec![
5294 ANFBinding {
5295 name: "t0".to_string(),
5296 value: ANFValue::LoadParam { name: "preimage".to_string() },
5297 source_loc: None,
5298 },
5299 ANFBinding {
5300 name: "t1".to_string(),
5301 value: ANFValue::Call {
5302 func: "extractOutputs".to_string(),
5303 args: vec!["t0".to_string()],
5304 },
5305 source_loc: None,
5306 },
5307 ANFBinding {
5308 name: "t2".to_string(),
5309 value: ANFValue::Assert { value: "t1".to_string() },
5310 source_loc: None,
5311 },
5312 ],
5313 is_public: true,
5314 }],
5315 };
5316
5317 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5318 let opcodes = collect_all_opcodes(&methods[0].ops);
5319
5320 assert!(
5323 opcodes.contains(&"PUSH(40)".to_string()),
5324 "expected PUSH(40) for extractOutputs offset, got: {:?}",
5325 opcodes
5326 );
5327 assert!(
5329 !opcodes.contains(&"PUSH(44)".to_string()),
5330 "extractOutputs should NOT use offset 44, got: {:?}",
5331 opcodes
5332 );
5333 }
5334
5335 #[test]
5341 fn test_arithmetic_ops_contains_add() {
5342 let program = ANFProgram {
5344 contract_name: "ArithCheck".to_string(),
5345 properties: vec![ANFProperty {
5346 name: "target".to_string(),
5347 prop_type: "bigint".to_string(),
5348 readonly: true,
5349 initial_value: None,
5350 }],
5351 methods: vec![ANFMethod {
5352 name: "verify".to_string(),
5353 params: vec![
5354 ANFParam { name: "a".to_string(), param_type: "bigint".to_string() },
5355 ANFParam { name: "b".to_string(), param_type: "bigint".to_string() },
5356 ],
5357 body: vec![
5358 ANFBinding {
5359 name: "t0".to_string(),
5360 value: ANFValue::LoadParam { name: "a".to_string() },
5361 source_loc: None,
5362 },
5363 ANFBinding {
5364 name: "t1".to_string(),
5365 value: ANFValue::LoadParam { name: "b".to_string() },
5366 source_loc: None,
5367 },
5368 ANFBinding {
5369 name: "t2".to_string(),
5370 value: ANFValue::BinOp {
5371 op: "+".to_string(),
5372 left: "t0".to_string(),
5373 right: "t1".to_string(),
5374 result_type: None,
5375 },
5376 source_loc: None,
5377 },
5378 ANFBinding {
5379 name: "t3".to_string(),
5380 value: ANFValue::LoadProp { name: "target".to_string() },
5381 source_loc: None,
5382 },
5383 ANFBinding {
5384 name: "t4".to_string(),
5385 value: ANFValue::BinOp {
5386 op: "===".to_string(),
5387 left: "t2".to_string(),
5388 right: "t3".to_string(),
5389 result_type: None,
5390 },
5391 source_loc: None,
5392 },
5393 ANFBinding {
5394 name: "t5".to_string(),
5395 value: ANFValue::Assert { value: "t4".to_string() },
5396 source_loc: None,
5397 },
5398 ],
5399 is_public: true,
5400 }],
5401 };
5402
5403 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5404 let opcodes = collect_all_opcodes(&methods[0].ops);
5405
5406 assert!(
5408 opcodes.contains(&"OP_ADD".to_string()),
5409 "expected OP_ADD in stack ops for 'a + b', got: {:?}",
5410 opcodes
5411 );
5412
5413 assert!(
5415 opcodes.contains(&"OP_NUMEQUAL".to_string()),
5416 "expected OP_NUMEQUAL in stack ops for '===', got: {:?}",
5417 opcodes
5418 );
5419 }
5420
5421 #[test]
5427 fn test_s18_pick_roll_depth_within_max_stack_depth() {
5428 let program = p2pkh_program();
5429 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5430
5431 let max_depth = methods[0].max_stack_depth;
5432
5433 fn check_ops(ops: &[StackOp], max_depth: usize) {
5434 for op in ops {
5435 match op {
5436 StackOp::Pick { depth } => {
5437 assert!(
5438 *depth < max_depth,
5439 "Pick depth {} must be < max_stack_depth {}",
5440 depth,
5441 max_depth
5442 );
5443 }
5444 StackOp::Roll { depth } => {
5445 assert!(
5446 *depth < max_depth,
5447 "Roll depth {} must be < max_stack_depth {}",
5448 depth,
5449 max_depth
5450 );
5451 }
5452 StackOp::If { then_ops, else_ops } => {
5453 check_ops(then_ops, max_depth);
5454 check_ops(else_ops, max_depth);
5455 }
5456 _ => {}
5457 }
5458 }
5459 }
5460
5461 check_ops(&methods[0].ops, max_depth);
5462 }
5463
5464 #[test]
5470 fn test_bytestring_concat_emits_op_cat() {
5471 let program = ANFProgram {
5472 contract_name: "CatCheck".to_string(),
5473 properties: vec![],
5474 methods: vec![ANFMethod {
5475 name: "verify".to_string(),
5476 params: vec![
5477 ANFParam { name: "a".to_string(), param_type: "ByteString".to_string() },
5478 ANFParam { name: "b".to_string(), param_type: "ByteString".to_string() },
5479 ANFParam { name: "expected".to_string(), param_type: "ByteString".to_string() },
5480 ],
5481 body: vec![
5482 ANFBinding {
5483 name: "t0".to_string(),
5484 value: ANFValue::LoadParam { name: "a".to_string() },
5485 source_loc: None,
5486 },
5487 ANFBinding {
5488 name: "t1".to_string(),
5489 value: ANFValue::LoadParam { name: "b".to_string() },
5490 source_loc: None,
5491 },
5492 ANFBinding {
5493 name: "t2".to_string(),
5494 value: ANFValue::BinOp {
5495 op: "+".to_string(),
5496 left: "t0".to_string(),
5497 right: "t1".to_string(),
5498 result_type: Some("bytes".to_string()), },
5500 source_loc: None,
5501 },
5502 ANFBinding {
5503 name: "t3".to_string(),
5504 value: ANFValue::LoadParam { name: "expected".to_string() },
5505 source_loc: None,
5506 },
5507 ANFBinding {
5508 name: "t4".to_string(),
5509 value: ANFValue::BinOp {
5510 op: "===".to_string(),
5511 left: "t2".to_string(),
5512 right: "t3".to_string(),
5513 result_type: Some("bytes".to_string()),
5514 },
5515 source_loc: None,
5516 },
5517 ANFBinding {
5518 name: "t5".to_string(),
5519 value: ANFValue::Assert { value: "t4".to_string() },
5520 source_loc: None,
5521 },
5522 ],
5523 is_public: true,
5524 }],
5525 };
5526
5527 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5528 let opcodes = collect_all_opcodes(&methods[0].ops);
5529
5530 assert!(
5531 opcodes.contains(&"OP_CAT".to_string()),
5532 "ByteString '+' (result_type='bytes') should emit OP_CAT; got opcodes: {:?}",
5533 opcodes
5534 );
5535 assert!(
5536 !opcodes.contains(&"OP_ADD".to_string()),
5537 "ByteString '+' should NOT emit OP_ADD (that's for bigint); got opcodes: {:?}",
5538 opcodes
5539 );
5540 }
5541
5542 #[test]
5548 fn test_log2_emits_64_if_ops() {
5549 let program = ANFProgram {
5550 contract_name: "TestLog2Count".to_string(),
5551 properties: vec![],
5552 methods: vec![ANFMethod {
5553 name: "check".to_string(),
5554 params: vec![
5555 ANFParam { name: "n".to_string(), param_type: "bigint".to_string() },
5556 ],
5557 body: vec![
5558 ANFBinding {
5559 name: "t0".to_string(),
5560 value: ANFValue::LoadParam { name: "n".to_string() },
5561 source_loc: None,
5562 },
5563 ANFBinding {
5564 name: "t1".to_string(),
5565 value: ANFValue::Call {
5566 func: "log2".to_string(),
5567 args: vec!["t0".to_string()],
5568 },
5569 source_loc: None,
5570 },
5571 ANFBinding {
5572 name: "t2".to_string(),
5573 value: ANFValue::LoadConst {
5574 value: serde_json::Value::Number(serde_json::Number::from(0)),
5575 },
5576 source_loc: None,
5577 },
5578 ANFBinding {
5579 name: "t3".to_string(),
5580 value: ANFValue::BinOp {
5581 op: ">=".to_string(),
5582 left: "t1".to_string(),
5583 right: "t2".to_string(),
5584 result_type: None,
5585 },
5586 source_loc: None,
5587 },
5588 ANFBinding {
5589 name: "t4".to_string(),
5590 value: ANFValue::Assert { value: "t3".to_string() },
5591 source_loc: None,
5592 },
5593 ],
5594 is_public: true,
5595 }],
5596 };
5597
5598 let methods = lower_to_stack(&program).expect("stack lowering should succeed");
5599
5600 fn count_if_ops(ops: &[StackOp]) -> usize {
5602 let mut count = 0;
5603 for op in ops {
5604 match op {
5605 StackOp::If { then_ops, else_ops } => {
5606 count += 1;
5607 count += count_if_ops(then_ops);
5608 count += count_if_ops(else_ops);
5609 }
5610 _ => {}
5611 }
5612 }
5613 count
5614 }
5615
5616 let if_count = count_if_ops(&methods[0].ops);
5617 assert_eq!(
5618 if_count, 64,
5619 "log2 should emit exactly 64 if-ops (one per bit); got {} if-ops",
5620 if_count
5621 );
5622 }
5623}