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