1use chia_bls::PublicKey;
2use chia_protocol::{Bytes32, Coin};
3use chia_puzzle_types::{
4 cat::{CatSolution, EverythingWithSignatureTailArgs, GenesisByCoinIdTailArgs},
5 CoinProof, LineageProof, Memos,
6};
7use chia_sdk_types::{
8 conditions::{CreateCoin, RunCatTail},
9 puzzles::RevocationSolution,
10 run_puzzle, Condition, Conditions,
11};
12use clvm_traits::FromClvm;
13use clvm_utils::ToTreeHash;
14use clvmr::{Allocator, NodePtr};
15
16use crate::{CatLayer, DriverError, Layer, Puzzle, RevocationLayer, Spend, SpendContext};
17
18mod cat_info;
19mod cat_spend;
20mod single_cat_spend;
21
22pub use cat_info::*;
23pub use cat_spend::*;
24pub use single_cat_spend::*;
25
26#[must_use]
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct Cat {
38 pub coin: Coin,
40
41 pub lineage_proof: Option<LineageProof>,
50
51 pub info: CatInfo,
53}
54
55impl Cat {
56 pub fn new(coin: Coin, lineage_proof: Option<LineageProof>, info: CatInfo) -> Self {
57 Self {
58 coin,
59 lineage_proof,
60 info,
61 }
62 }
63
64 pub fn issue_with_coin(
65 ctx: &mut SpendContext,
66 parent_coin_id: Bytes32,
67 amount: u64,
68 extra_conditions: Conditions,
69 ) -> Result<(Conditions, Vec<Cat>), DriverError> {
70 let tail = ctx.curry(GenesisByCoinIdTailArgs::new(parent_coin_id))?;
71
72 Self::issue(
73 ctx,
74 parent_coin_id,
75 None,
76 amount,
77 RunCatTail::new(tail, NodePtr::NIL),
78 extra_conditions,
79 )
80 }
81
82 pub fn issue_with_key(
83 ctx: &mut SpendContext,
84 parent_coin_id: Bytes32,
85 public_key: PublicKey,
86 amount: u64,
87 extra_conditions: Conditions,
88 ) -> Result<(Conditions, Vec<Cat>), DriverError> {
89 let tail = ctx.curry(EverythingWithSignatureTailArgs::new(public_key))?;
90
91 Self::issue(
92 ctx,
93 parent_coin_id,
94 None,
95 amount,
96 RunCatTail::new(tail, NodePtr::NIL),
97 extra_conditions,
98 )
99 }
100
101 pub fn issue_revocable_with_coin(
102 ctx: &mut SpendContext,
103 parent_coin_id: Bytes32,
104 hidden_puzzle_hash: Bytes32,
105 amount: u64,
106 extra_conditions: Conditions,
107 ) -> Result<(Conditions, Vec<Cat>), DriverError> {
108 let tail = ctx.curry(GenesisByCoinIdTailArgs::new(parent_coin_id))?;
109
110 Self::issue(
111 ctx,
112 parent_coin_id,
113 Some(hidden_puzzle_hash),
114 amount,
115 RunCatTail::new(tail, NodePtr::NIL),
116 extra_conditions,
117 )
118 }
119
120 pub fn issue_revocable_with_key(
121 ctx: &mut SpendContext,
122 parent_coin_id: Bytes32,
123 public_key: PublicKey,
124 hidden_puzzle_hash: Bytes32,
125 amount: u64,
126 extra_conditions: Conditions,
127 ) -> Result<(Conditions, Vec<Cat>), DriverError> {
128 let tail = ctx.curry(EverythingWithSignatureTailArgs::new(public_key))?;
129
130 Self::issue(
131 ctx,
132 parent_coin_id,
133 Some(hidden_puzzle_hash),
134 amount,
135 RunCatTail::new(tail, NodePtr::NIL),
136 extra_conditions,
137 )
138 }
139
140 pub fn issue(
141 ctx: &mut SpendContext,
142 parent_coin_id: Bytes32,
143 hidden_puzzle_hash: Option<Bytes32>,
144 amount: u64,
145 run_tail: RunCatTail<NodePtr, NodePtr>,
146 conditions: Conditions,
147 ) -> Result<(Conditions, Vec<Cat>), DriverError> {
148 let delegated_spend = ctx.delegated_spend(conditions.with(run_tail))?;
149 let eve_info = CatInfo::new(
150 ctx.tree_hash(run_tail.program).into(),
151 hidden_puzzle_hash,
152 ctx.tree_hash(delegated_spend.puzzle).into(),
153 );
154
155 let eve = Cat::new(
156 Coin::new(parent_coin_id, eve_info.puzzle_hash().into(), amount),
157 None,
158 eve_info,
159 );
160
161 let children = Cat::spend_all(ctx, &[CatSpend::new(eve, delegated_spend)])?;
162
163 Ok((
164 Conditions::new().create_coin(eve.coin.puzzle_hash, eve.coin.amount, Memos::None),
165 children,
166 ))
167 }
168
169 pub fn spend_all(
182 ctx: &mut SpendContext,
183 cat_spends: &[CatSpend],
184 ) -> Result<Vec<Cat>, DriverError> {
185 let len = cat_spends.len();
186
187 let mut total_delta = 0;
188 let mut prev_subtotals = Vec::new();
189 let mut run_tail_index = None;
190 let mut children = Vec::new();
191
192 for (index, &item) in cat_spends.iter().enumerate() {
193 let output = ctx.run(item.spend.puzzle, item.spend.solution)?;
194 let conditions: Vec<Condition> = ctx.extract(output)?;
195
196 if run_tail_index.is_none() && conditions.iter().any(Condition::is_run_cat_tail) {
198 run_tail_index = Some(index);
199 }
200
201 let create_coins: Vec<CreateCoin<NodePtr>> = conditions
202 .into_iter()
203 .filter_map(Condition::into_create_coin)
204 .collect();
205
206 let delta = create_coins
208 .iter()
209 .fold(i128::from(item.cat.coin.amount), |delta, create_coin| {
210 delta - i128::from(create_coin.amount)
211 });
212
213 prev_subtotals.push(total_delta);
215
216 total_delta += delta;
218
219 for create_coin in create_coins {
220 children.push(
221 item.cat
222 .child_from_p2_create_coin(ctx, create_coin, item.hidden),
223 );
224 }
225 }
226
227 if let Some(tail_index) = run_tail_index {
229 let tail_adjustment = -total_delta;
230
231 prev_subtotals
232 .iter_mut()
233 .skip(tail_index + 1)
234 .for_each(|subtotal| {
235 *subtotal += tail_adjustment;
236 });
237 }
238
239 for (index, item) in cat_spends.iter().enumerate() {
240 let prev = &cat_spends[if index == 0 { len - 1 } else { index - 1 }];
242 let next = &cat_spends[if index == len - 1 { 0 } else { index + 1 }];
243
244 let next_inner_puzzle_hash = next.cat.info.inner_puzzle_hash();
245
246 item.cat.spend(
247 ctx,
248 SingleCatSpend {
249 p2_spend: item.spend,
250 prev_coin_id: prev.cat.coin.coin_id(),
251 next_coin_proof: CoinProof {
252 parent_coin_info: next.cat.coin.parent_coin_info,
253 inner_puzzle_hash: next_inner_puzzle_hash.into(),
254 amount: next.cat.coin.amount,
255 },
256 prev_subtotal: prev_subtotals[index].try_into()?,
257 extra_delta: if run_tail_index.is_some_and(|i| i == index) {
259 -total_delta.try_into()?
260 } else {
261 0
262 },
263 revoke: item.hidden,
264 },
265 )?;
266 }
267
268 Ok(children)
269 }
270
271 pub fn spend(&self, ctx: &mut SpendContext, info: SingleCatSpend) -> Result<(), DriverError> {
278 let mut spend = info.p2_spend;
279
280 if let Some(hidden_puzzle_hash) = self.info.hidden_puzzle_hash {
281 spend = RevocationLayer::new(hidden_puzzle_hash, self.info.p2_puzzle_hash)
282 .construct_spend(
283 ctx,
284 RevocationSolution::new(info.revoke, spend.puzzle, spend.solution),
285 )?;
286 }
287
288 spend = CatLayer::new(self.info.asset_id, spend.puzzle).construct_spend(
289 ctx,
290 CatSolution {
291 lineage_proof: self.lineage_proof,
292 inner_puzzle_solution: spend.solution,
293 prev_coin_id: info.prev_coin_id,
294 this_coin_info: self.coin,
295 next_coin_proof: info.next_coin_proof,
296 extra_delta: info.extra_delta,
297 prev_subtotal: info.prev_subtotal,
298 },
299 )?;
300
301 ctx.spend(self.coin, spend)?;
302
303 Ok(())
304 }
305
306 pub fn child_lineage_proof(&self) -> LineageProof {
308 LineageProof {
309 parent_parent_coin_info: self.coin.parent_coin_info,
310 parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
311 parent_amount: self.coin.amount,
312 }
313 }
314
315 pub fn child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self {
320 self.child_with(
321 CatInfo {
322 p2_puzzle_hash,
323 ..self.info
324 },
325 amount,
326 )
327 }
328
329 pub fn unrevocable_child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self {
334 self.child_with(
335 CatInfo {
336 p2_puzzle_hash,
337 hidden_puzzle_hash: None,
338 ..self.info
339 },
340 amount,
341 )
342 }
343
344 pub fn child_with(&self, info: CatInfo, amount: u64) -> Self {
349 Self {
350 coin: Coin::new(self.coin.coin_id(), info.puzzle_hash().into(), amount),
351 lineage_proof: Some(self.child_lineage_proof()),
352 info,
353 }
354 }
355
356 pub fn parse(
361 allocator: &Allocator,
362 coin: Coin,
363 puzzle: Puzzle,
364 solution: NodePtr,
365 ) -> Result<Option<(Self, Puzzle, NodePtr)>, DriverError> {
366 let Some(cat_layer) = CatLayer::<Puzzle>::parse_puzzle(allocator, puzzle)? else {
367 return Ok(None);
368 };
369 let cat_solution = CatLayer::<Puzzle>::parse_solution(allocator, solution)?;
370
371 if let Some(revocation_layer) =
372 RevocationLayer::parse_puzzle(allocator, cat_layer.inner_puzzle)?
373 {
374 let revocation_solution =
375 RevocationLayer::parse_solution(allocator, cat_solution.inner_puzzle_solution)?;
376
377 let info = Self::new(
378 coin,
379 cat_solution.lineage_proof,
380 CatInfo::new(
381 cat_layer.asset_id,
382 Some(revocation_layer.hidden_puzzle_hash),
383 revocation_layer.inner_puzzle_hash,
384 ),
385 );
386
387 Ok(Some((
388 info,
389 Puzzle::parse(allocator, revocation_solution.puzzle),
390 revocation_solution.solution,
391 )))
392 } else {
393 let info = Self::new(
394 coin,
395 cat_solution.lineage_proof,
396 CatInfo::new(
397 cat_layer.asset_id,
398 None,
399 cat_layer.inner_puzzle.curried_puzzle_hash().into(),
400 ),
401 );
402
403 Ok(Some((
404 info,
405 cat_layer.inner_puzzle,
406 cat_solution.inner_puzzle_solution,
407 )))
408 }
409 }
410
411 pub fn parse_children(
420 allocator: &mut Allocator,
421 parent_coin: Coin,
422 parent_puzzle: Puzzle,
423 parent_solution: NodePtr,
424 ) -> Result<Option<Vec<Self>>, DriverError> {
425 let Some(parent_layer) = CatLayer::<Puzzle>::parse_puzzle(allocator, parent_puzzle)? else {
426 return Ok(None);
427 };
428 let parent_solution = CatLayer::<Puzzle>::parse_solution(allocator, parent_solution)?;
429
430 let mut hidden_puzzle_hash = None;
431 let mut p2_puzzle_hash = parent_layer.inner_puzzle.curried_puzzle_hash().into();
432 let mut inner_spend = Spend::new(
433 parent_layer.inner_puzzle.ptr(),
434 parent_solution.inner_puzzle_solution,
435 );
436 let mut revoke = false;
437
438 if let Some(revocation_layer) =
439 RevocationLayer::parse_puzzle(allocator, parent_layer.inner_puzzle)?
440 {
441 hidden_puzzle_hash = Some(revocation_layer.hidden_puzzle_hash);
442 p2_puzzle_hash = revocation_layer.inner_puzzle_hash;
443
444 let revocation_solution =
445 RevocationLayer::parse_solution(allocator, parent_solution.inner_puzzle_solution)?;
446
447 inner_spend = Spend::new(revocation_solution.puzzle, revocation_solution.solution);
448 revoke = revocation_solution.hidden;
449 }
450
451 let cat = Cat::new(
452 parent_coin,
453 parent_solution.lineage_proof,
454 CatInfo::new(parent_layer.asset_id, hidden_puzzle_hash, p2_puzzle_hash),
455 );
456
457 let output = run_puzzle(allocator, inner_spend.puzzle, inner_spend.solution)?;
458 let conditions = Vec::<Condition>::from_clvm(allocator, output)?;
459
460 let outputs = conditions
461 .into_iter()
462 .filter_map(Condition::into_create_coin)
463 .map(|create_coin| cat.child_from_p2_create_coin(allocator, create_coin, revoke))
464 .collect();
465
466 Ok(Some(outputs))
467 }
468
469 pub fn child_from_p2_create_coin(
477 &self,
478 allocator: &Allocator,
479 create_coin: CreateCoin<NodePtr>,
480 revoke: bool,
481 ) -> Self {
482 let child = self.child(create_coin.puzzle_hash, create_coin.amount);
484
485 let Some(hidden_puzzle_hash) = self.info.hidden_puzzle_hash else {
487 return child;
488 };
489
490 if !revoke {
492 return child;
493 }
494
495 let unrevocable_child = self.unrevocable_child(create_coin.puzzle_hash, create_coin.amount);
497
498 let Memos::Some(memos) = create_coin.memos else {
500 return unrevocable_child;
501 };
502
503 let Some((hint, _)) = <(Bytes32, NodePtr)>::from_clvm(allocator, memos).ok() else {
504 return unrevocable_child;
505 };
506
507 if create_coin.puzzle_hash
510 == RevocationLayer::new(hidden_puzzle_hash, hint)
511 .tree_hash()
512 .into()
513 {
514 return self.child(hint, create_coin.amount);
515 }
516
517 unrevocable_child
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use std::slice;
527
528 use chia_puzzle_types::cat::EverythingWithSignatureTailArgs;
529 use chia_sdk_test::Simulator;
530 use chia_sdk_types::{puzzles::RevocationArgs, Mod};
531 use rstest::rstest;
532
533 use crate::{SpendWithConditions, StandardLayer};
534
535 use super::*;
536
537 #[test]
538 fn test_single_issuance_cat() -> anyhow::Result<()> {
539 let mut sim = Simulator::new();
540 let ctx = &mut SpendContext::new();
541
542 let alice = sim.bls(1);
543 let alice_p2 = StandardLayer::new(alice.pk);
544
545 let memos = ctx.hint(alice.puzzle_hash)?;
546 let (issue_cat, cats) = Cat::issue_with_coin(
547 ctx,
548 alice.coin.coin_id(),
549 1,
550 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
551 )?;
552 alice_p2.spend(ctx, alice.coin, issue_cat)?;
553
554 sim.spend_coins(ctx.take(), &[alice.sk])?;
555
556 let cat = cats[0];
557 assert_eq!(cat.info.p2_puzzle_hash, alice.puzzle_hash);
558 assert_eq!(
559 cat.info.asset_id,
560 GenesisByCoinIdTailArgs::curry_tree_hash(alice.coin.coin_id()).into()
561 );
562 assert!(sim.coin_state(cat.coin.coin_id()).is_some());
563
564 Ok(())
565 }
566
567 #[test]
568 fn test_multi_issuance_cat() -> anyhow::Result<()> {
569 let mut sim = Simulator::new();
570 let ctx = &mut SpendContext::new();
571
572 let alice = sim.bls(1);
573 let alice_p2 = StandardLayer::new(alice.pk);
574
575 let memos = ctx.hint(alice.puzzle_hash)?;
576 let (issue_cat, cats) = Cat::issue_with_key(
577 ctx,
578 alice.coin.coin_id(),
579 alice.pk,
580 1,
581 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
582 )?;
583 alice_p2.spend(ctx, alice.coin, issue_cat)?;
584 sim.spend_coins(ctx.take(), &[alice.sk])?;
585
586 let cat = cats[0];
587 assert_eq!(cat.info.p2_puzzle_hash, alice.puzzle_hash);
588 assert_eq!(
589 cat.info.asset_id,
590 EverythingWithSignatureTailArgs::curry_tree_hash(alice.pk).into()
591 );
592 assert!(sim.coin_state(cat.coin.coin_id()).is_some());
593
594 Ok(())
595 }
596
597 #[test]
598 fn test_zero_cat_issuance() -> anyhow::Result<()> {
599 let mut sim = Simulator::new();
600 let ctx = &mut SpendContext::new();
601
602 let alice = sim.bls(0);
603 let alice_p2 = StandardLayer::new(alice.pk);
604
605 let memos = ctx.hint(alice.puzzle_hash)?;
606 let (issue_cat, cats) = Cat::issue_with_coin(
607 ctx,
608 alice.coin.coin_id(),
609 0,
610 Conditions::new().create_coin(alice.puzzle_hash, 0, memos),
611 )?;
612 alice_p2.spend(ctx, alice.coin, issue_cat)?;
613
614 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
615
616 let cat = cats[0];
617 assert_eq!(cat.info.p2_puzzle_hash, alice.puzzle_hash);
618 assert_eq!(
619 cat.info.asset_id,
620 GenesisByCoinIdTailArgs::curry_tree_hash(alice.coin.coin_id()).into()
621 );
622 assert!(sim.coin_state(cat.coin.coin_id()).is_some());
623
624 let cat_spend = CatSpend::new(
625 cat,
626 alice_p2.spend_with_conditions(
627 ctx,
628 Conditions::new().create_coin(alice.puzzle_hash, 0, memos),
629 )?,
630 );
631 Cat::spend_all(ctx, &[cat_spend])?;
632 sim.spend_coins(ctx.take(), &[alice.sk])?;
633
634 Ok(())
635 }
636
637 #[test]
638 fn test_missing_cat_issuance_output() -> anyhow::Result<()> {
639 let mut sim = Simulator::new();
640 let ctx = &mut SpendContext::new();
641
642 let alice = sim.bls(1);
643 let alice_p2 = StandardLayer::new(alice.pk);
644
645 let (issue_cat, _cats) =
646 Cat::issue_with_coin(ctx, alice.coin.coin_id(), 1, Conditions::new())?;
647 alice_p2.spend(ctx, alice.coin, issue_cat)?;
648
649 assert_eq!(
650 sim.spend_coins(ctx.take(), &[alice.sk])
651 .unwrap_err()
652 .to_string(),
653 "Signer error: Eval error: Error at NodePtr(SmallAtom, 0): clvm raise"
654 );
655
656 Ok(())
657 }
658
659 #[test]
660 fn test_exceeded_cat_issuance_output() -> anyhow::Result<()> {
661 let mut sim = Simulator::new();
662 let ctx = &mut SpendContext::new();
663
664 let alice = sim.bls(2);
665 let alice_p2 = StandardLayer::new(alice.pk);
666
667 let memos = ctx.hint(alice.puzzle_hash)?;
668 let (issue_cat, _cats) = Cat::issue_with_coin(
669 ctx,
670 alice.coin.coin_id(),
671 1,
672 Conditions::new().create_coin(alice.puzzle_hash, 2, memos),
673 )?;
674 alice_p2.spend(ctx, alice.coin, issue_cat)?;
675
676 assert_eq!(
677 sim.spend_coins(ctx.take(), &[alice.sk])
678 .unwrap_err()
679 .to_string(),
680 "Signer error: Eval error: Error at NodePtr(SmallAtom, 0): clvm raise"
681 );
682
683 Ok(())
684 }
685
686 #[rstest]
687 #[case(1)]
688 #[case(2)]
689 #[case(3)]
690 #[case(10)]
691 fn test_cat_spends(#[case] coins: usize) -> anyhow::Result<()> {
692 let mut sim = Simulator::new();
693 let ctx = &mut SpendContext::new();
694
695 let mut amounts = Vec::with_capacity(coins);
697
698 for amount in 0..coins {
699 amounts.push(amount as u64);
700 }
701
702 let sum = amounts.iter().sum::<u64>();
704
705 let alice = sim.bls(sum);
706 let alice_p2 = StandardLayer::new(alice.pk);
707
708 let mut conditions = Conditions::new();
710
711 let memos = ctx.hint(alice.puzzle_hash)?;
712 for &amount in &amounts {
713 conditions = conditions.create_coin(alice.puzzle_hash, amount, memos);
714 }
715
716 let (issue_cat, mut cats) =
717 Cat::issue_with_coin(ctx, alice.coin.coin_id(), sum, conditions)?;
718 alice_p2.spend(ctx, alice.coin, issue_cat)?;
719
720 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
721
722 for _ in 0..3 {
724 let cat_spends: Vec<CatSpend> = cats
725 .iter()
726 .map(|cat| {
727 Ok(CatSpend::new(
728 *cat,
729 alice_p2.spend_with_conditions(
730 ctx,
731 Conditions::new().create_coin(
732 alice.puzzle_hash,
733 cat.coin.amount,
734 memos,
735 ),
736 )?,
737 ))
738 })
739 .collect::<anyhow::Result<_>>()?;
740
741 cats = Cat::spend_all(ctx, &cat_spends)?;
742 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
743 }
744
745 Ok(())
746 }
747
748 #[test]
749 fn test_different_cat_p2_puzzles() -> anyhow::Result<()> {
750 let mut sim = Simulator::new();
751 let ctx = &mut SpendContext::new();
752
753 let alice = sim.bls(2);
754 let alice_p2 = StandardLayer::new(alice.pk);
755
756 let custom_p2 = ctx.alloc(&1)?;
758 let custom_p2_puzzle_hash = ctx.tree_hash(custom_p2).into();
759
760 let memos = ctx.hint(alice.puzzle_hash)?;
761 let custom_memos = ctx.hint(custom_p2_puzzle_hash)?;
762 let (issue_cat, cats) = Cat::issue_with_coin(
763 ctx,
764 alice.coin.coin_id(),
765 2,
766 Conditions::new()
767 .create_coin(alice.puzzle_hash, 1, memos)
768 .create_coin(custom_p2_puzzle_hash, 1, custom_memos),
769 )?;
770 alice_p2.spend(ctx, alice.coin, issue_cat)?;
771 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
772
773 let spends = [
774 CatSpend::new(
775 cats[0],
776 alice_p2.spend_with_conditions(
777 ctx,
778 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
779 )?,
780 ),
781 CatSpend::new(
782 cats[1],
783 Spend::new(
784 custom_p2,
785 ctx.alloc(&[CreateCoin::new(custom_p2_puzzle_hash, 1, custom_memos)])?,
786 ),
787 ),
788 ];
789
790 Cat::spend_all(ctx, &spends)?;
791 sim.spend_coins(ctx.take(), &[alice.sk])?;
792
793 Ok(())
794 }
795
796 #[test]
797 fn test_cat_melt() -> anyhow::Result<()> {
798 let mut sim = Simulator::new();
799 let ctx = &mut SpendContext::new();
800
801 let alice = sim.bls(10000);
802 let alice_p2 = StandardLayer::new(alice.pk);
803 let hint = ctx.hint(alice.puzzle_hash)?;
804
805 let conditions = Conditions::new().create_coin(alice.puzzle_hash, 10000, hint);
806
807 let (issue_cat, cats) =
808 Cat::issue_with_key(ctx, alice.coin.coin_id(), alice.pk, 10000, conditions)?;
809
810 alice_p2.spend(ctx, alice.coin, issue_cat)?;
811
812 let tail = ctx.curry(EverythingWithSignatureTailArgs::new(alice.pk))?;
813
814 let cat_spend = CatSpend::new(
815 cats[0],
816 alice_p2.spend_with_conditions(
817 ctx,
818 Conditions::new()
819 .create_coin(alice.puzzle_hash, 7000, hint)
820 .run_cat_tail(tail, NodePtr::NIL),
821 )?,
822 );
823
824 Cat::spend_all(ctx, &[cat_spend])?;
825
826 sim.spend_coins(ctx.take(), &[alice.sk])?;
827
828 Ok(())
829 }
830
831 #[rstest]
832 fn test_cat_tail_reveal(
833 #[values(0, 1, 2)] tail_index: usize,
834 #[values(true, false)] melt: bool,
835 ) -> anyhow::Result<()> {
836 let mut sim = Simulator::new();
837 let ctx = &mut SpendContext::new();
838
839 let alice = sim.bls(15000);
840 let alice_p2 = StandardLayer::new(alice.pk);
841 let hint = ctx.hint(alice.puzzle_hash)?;
842
843 let conditions = Conditions::new()
844 .create_coin(alice.puzzle_hash, 3000, hint)
845 .create_coin(alice.puzzle_hash, 6000, hint)
846 .create_coin(alice.puzzle_hash, 1000, hint);
847
848 let (issue_cat, cats) =
849 Cat::issue_with_key(ctx, alice.coin.coin_id(), alice.pk, 10000, conditions)?;
850
851 alice_p2.spend(ctx, alice.coin, issue_cat)?;
852
853 let tail = ctx.curry(EverythingWithSignatureTailArgs::new(alice.pk))?;
854
855 let cat_spends = cats
856 .into_iter()
857 .enumerate()
858 .map(|(i, cat)| {
859 let mut conditions = Conditions::new();
860
861 if i == tail_index {
863 conditions.push(RunCatTail::new(tail, NodePtr::NIL));
864
865 if !melt {
866 conditions.push(CreateCoin::new(alice.puzzle_hash, 15000, hint));
867 }
868 }
869
870 Ok(CatSpend::new(
871 cat,
872 alice_p2.spend_with_conditions(ctx, conditions)?,
873 ))
874 })
875 .collect::<anyhow::Result<Vec<_>>>()?;
876
877 Cat::spend_all(ctx, &cat_spends)?;
878
879 sim.spend_coins(ctx.take(), &[alice.sk])?;
880
881 Ok(())
882 }
883
884 #[test]
885 fn test_revocable_cat() -> anyhow::Result<()> {
886 let mut sim = Simulator::new();
887 let mut ctx = SpendContext::new();
888
889 let alice = sim.bls(10);
890 let alice_p2 = StandardLayer::new(alice.pk);
891
892 let bob = sim.bls(0);
893 let bob_p2 = StandardLayer::new(bob.pk);
894
895 let asset_id = EverythingWithSignatureTailArgs::curry_tree_hash(alice.pk).into();
896 let hint = ctx.hint(bob.puzzle_hash)?;
897
898 let (issue_cat, cats) = Cat::issue_revocable_with_key(
899 &mut ctx,
900 alice.coin.coin_id(),
901 alice.pk,
902 alice.puzzle_hash,
903 10,
904 Conditions::new().create_coin(bob.puzzle_hash, 10, hint),
905 )?;
906 alice_p2.spend(&mut ctx, alice.coin, issue_cat)?;
907
908 let cat_spend = CatSpend::new(
910 cats[0],
911 bob_p2.spend_with_conditions(
912 &mut ctx,
913 Conditions::new().create_coin(bob.puzzle_hash, 10, hint),
914 )?,
915 );
916 let cats = Cat::spend_all(&mut ctx, &[cat_spend])?;
917
918 let hint = ctx.hint(alice.puzzle_hash)?;
920
921 let revocable_puzzle_hash = RevocationArgs::new(alice.puzzle_hash, alice.puzzle_hash)
922 .curry_tree_hash()
923 .into();
924
925 let cat_spend = CatSpend::revoke(
926 cats[0],
927 alice_p2.spend_with_conditions(
928 &mut ctx,
929 Conditions::new()
930 .create_coin(alice.puzzle_hash, 5, hint)
931 .create_coin(revocable_puzzle_hash, 5, hint),
932 )?,
933 );
934
935 let cats = Cat::spend_all(&mut ctx, &[cat_spend])?;
936
937 sim.spend_coins(ctx.take(), &[alice.sk.clone(), bob.sk.clone()])?;
939
940 assert_ne!(sim.coin_state(cats[0].coin.coin_id()), None);
942 assert_eq!(cats[0].info.p2_puzzle_hash, alice.puzzle_hash);
943 assert_eq!(cats[0].info.asset_id, asset_id);
944 assert_eq!(cats[0].info.hidden_puzzle_hash, None);
945
946 assert_ne!(sim.coin_state(cats[1].coin.coin_id()), None);
948 assert_eq!(cats[1].info.p2_puzzle_hash, alice.puzzle_hash);
949 assert_eq!(cats[1].info.asset_id, asset_id);
950 assert_eq!(cats[1].info.hidden_puzzle_hash, Some(alice.puzzle_hash));
951
952 let lineage_proof = cats[0].lineage_proof;
953
954 let parent_spend = sim.coin_spend(cats[0].coin.parent_coin_info).unwrap();
955 let parent_puzzle = ctx.alloc(&parent_spend.puzzle_reveal)?;
956 let parent_puzzle = Puzzle::parse(&ctx, parent_puzzle);
957 let parent_solution = ctx.alloc(&parent_spend.solution)?;
958
959 let cats =
960 Cat::parse_children(&mut ctx, parent_spend.coin, parent_puzzle, parent_solution)?
961 .unwrap();
962
963 assert_ne!(sim.coin_state(cats[0].coin.coin_id()), None);
965 assert_eq!(cats[0].info.p2_puzzle_hash, alice.puzzle_hash);
966 assert_eq!(cats[0].info.asset_id, asset_id);
967 assert_eq!(cats[0].info.hidden_puzzle_hash, None);
968
969 assert_ne!(sim.coin_state(cats[1].coin.coin_id()), None);
971 assert_eq!(cats[1].info.p2_puzzle_hash, alice.puzzle_hash);
972 assert_eq!(cats[1].info.asset_id, asset_id);
973 assert_eq!(cats[1].info.hidden_puzzle_hash, Some(alice.puzzle_hash));
974
975 assert_eq!(cats[0].lineage_proof, lineage_proof);
976
977 let cat_spends = cats
978 .into_iter()
979 .map(|cat| {
980 Ok(CatSpend::revoke(
981 cat,
982 alice_p2.spend_with_conditions(
983 &mut ctx,
984 Conditions::new().create_coin(alice.puzzle_hash, 5, hint),
985 )?,
986 ))
987 })
988 .collect::<anyhow::Result<Vec<_>>>()?;
989
990 _ = Cat::spend_all(&mut ctx, &cat_spends)?;
991
992 sim.spend_coins(ctx.take(), &[alice.sk, bob.sk])?;
994
995 Ok(())
996 }
997}