chia_sdk_driver/primitives/
cat.rs

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::{RevocationArgs, RevocationSolution},
10    run_puzzle, Condition, Conditions, Mod,
11};
12use clvm_traits::{clvm_quote, FromClvm};
13use clvm_utils::{tree_hash, 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/// Contains all information needed to spend the outer puzzles of CAT coins.
27/// The [`CatInfo`] is used to construct the puzzle, but the [`LineageProof`] is needed for the solution.
28///
29/// The only thing missing to create a valid coin spend is the inner puzzle and solution.
30/// However, this is handled separately to provide as much flexibility as possible.
31///
32/// This type should contain all of the information you need to store in a database for later.
33/// As long as you can figure out what puzzle the p2 puzzle hash corresponds to and spend it,
34/// you have enough information to spend the CAT coin.
35#[must_use]
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct Cat {
38    /// The coin that this [`Cat`] represents. Its puzzle hash should match the [`CatInfo::puzzle_hash`].
39    pub coin: Coin,
40
41    /// The lineage proof is needed by the CAT puzzle to prove that this coin is a legitimate CAT.
42    /// It's typically obtained by looking up and parsing the parent coin.
43    ///
44    /// This can get a bit tedious, so a helper method [`Cat::parse_children`] is provided to parse
45    /// the child [`Cat`] objects from the parent (once you have looked up its information on-chain).
46    ///
47    /// Note that while the lineage proof is needed for most coins, it is optional if you are
48    /// issuing more of the CAT by running its TAIL program.
49    pub lineage_proof: Option<LineageProof>,
50
51    /// The information needed to construct the outer puzzle of a CAT. See [`CatInfo`] for more details.
52    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            ctx.tree_hash(tail).into(),
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            ctx.tree_hash(tail).into(),
95            amount,
96            RunCatTail::new(tail, NodePtr::NIL),
97            extra_conditions,
98        )
99    }
100
101    pub fn issue(
102        ctx: &mut SpendContext,
103        parent_coin_id: Bytes32,
104        asset_id: Bytes32,
105        amount: u64,
106        run_tail: RunCatTail<NodePtr, NodePtr>,
107        conditions: Conditions,
108    ) -> Result<(Conditions, Vec<Cat>), DriverError> {
109        let p2_puzzle = ctx.alloc_hashed(&clvm_quote!(conditions.with(run_tail)))?;
110        let puzzle_hash = CatLayer::new(asset_id, p2_puzzle).tree_hash().into();
111
112        let eve = Cat::new(
113            Coin::new(parent_coin_id, puzzle_hash, amount),
114            None,
115            CatInfo::new(asset_id, None, p2_puzzle.tree_hash().into()),
116        );
117
118        let children = Cat::spend_all(
119            ctx,
120            &[CatSpend::new(
121                eve,
122                Spend::new(p2_puzzle.ptr(), NodePtr::NIL),
123            )],
124        )?;
125
126        Ok((
127            Conditions::new().create_coin(puzzle_hash, amount, Memos::None),
128            children,
129        ))
130    }
131
132    /// Constructs a [`CoinSpend`](chia_protocol::CoinSpend) for each [`CatSpend`] in the list.
133    /// The spends are added to the [`SpendContext`] (in order) for convenience.
134    ///
135    /// All of the ring announcements and proofs required by the CAT puzzle are calculated automatically.
136    /// This requires running the inner spends to get the conditions, so any errors will be propagated.
137    ///
138    /// It's important not to spend CATs with different asset IDs at the same time, since they are not
139    /// compatible.
140    ///
141    /// Additionally, you should group all CAT spends done in the same transaction together
142    /// so that the value of one coin can be freely used in the output of another. If you spend them
143    /// separately, there will be multiple announcement rings and a non-zero delta will be calculated.
144    pub fn spend_all(
145        ctx: &mut SpendContext,
146        cat_spends: &[CatSpend],
147    ) -> Result<Vec<Cat>, DriverError> {
148        let len = cat_spends.len();
149
150        let mut total_delta = 0;
151        let mut prev_subtotals = Vec::new();
152        let mut run_tail_index = None;
153        let mut children = Vec::new();
154
155        for (index, &item) in cat_spends.iter().enumerate() {
156            // Calculate the delta and add it to the subtotal.
157            let output = ctx.run(item.inner_spend.puzzle, item.inner_spend.solution)?;
158            let conditions: Vec<Condition> = ctx.extract(output)?;
159
160            if conditions.iter().any(Condition::is_run_cat_tail) {
161                run_tail_index = Some(index);
162            }
163
164            let create_coins: Vec<CreateCoin<NodePtr>> = conditions
165                .into_iter()
166                .filter_map(Condition::into_create_coin)
167                .collect();
168
169            let delta = create_coins
170                .iter()
171                .fold(i128::from(item.cat.coin.amount), |delta, create_coin| {
172                    delta - i128::from(create_coin.amount)
173                });
174
175            let prev_subtotal = total_delta;
176            total_delta += delta;
177
178            prev_subtotals.push(prev_subtotal);
179
180            for create_coin in create_coins {
181                children.push(
182                    item.cat
183                        .child_from_p2_create_coin(ctx, create_coin, item.revoke),
184                );
185            }
186        }
187
188        for (index, item) in cat_spends.iter().enumerate() {
189            // Find information of neighboring coins on the ring.
190            let prev = &cat_spends[if index == 0 { len - 1 } else { index - 1 }];
191            let next = &cat_spends[if index == len - 1 { 0 } else { index + 1 }];
192
193            let next_p2_puzzle_hash = ctx.tree_hash(next.inner_spend.puzzle).into();
194
195            item.cat.spend(
196                ctx,
197                SingleCatSpend {
198                    inner_spend: item.inner_spend,
199                    prev_coin_id: prev.cat.coin.coin_id(),
200                    next_coin_proof: CoinProof {
201                        parent_coin_info: next.cat.coin.parent_coin_info,
202                        inner_puzzle_hash: if let Some(hidden_puzzle_hash) =
203                            item.cat.info.hidden_puzzle_hash
204                        {
205                            RevocationArgs::new(hidden_puzzle_hash, next_p2_puzzle_hash)
206                                .curry_tree_hash()
207                                .into()
208                        } else {
209                            next_p2_puzzle_hash
210                        },
211                        amount: next.cat.coin.amount,
212                    },
213                    prev_subtotal: prev_subtotals[index].try_into()?,
214                    extra_delta: if run_tail_index.is_some_and(|i| i == index) {
215                        -total_delta.try_into()?
216                    } else {
217                        0
218                    },
219                    revoke: item.revoke,
220                },
221            )?;
222        }
223
224        Ok(children)
225    }
226
227    /// Spends this CAT coin with the provided solution parameters. Other parameters are inferred from
228    /// the [`Cat`] instance.
229    ///
230    /// This is useful if you have already calculated the conditions and want to spend the coin directly.
231    /// However, it's more common to use [`Cat::spend_all`] which handles the details of calculating the
232    /// solution (including ring announcements) for multiple CATs and spending them all at once.
233    pub fn spend(&self, ctx: &mut SpendContext, info: SingleCatSpend) -> Result<(), DriverError> {
234        let mut spend = info.inner_spend;
235
236        if let Some(hidden_puzzle_hash) = self.info.hidden_puzzle_hash {
237            spend = RevocationLayer::new(hidden_puzzle_hash, self.info.p2_puzzle_hash)
238                .construct_spend(
239                    ctx,
240                    RevocationSolution::new(info.revoke, spend.puzzle, spend.solution),
241                )?;
242        }
243
244        spend = CatLayer::new(self.info.asset_id, spend.puzzle).construct_spend(
245            ctx,
246            CatSolution {
247                lineage_proof: self.lineage_proof,
248                inner_puzzle_solution: spend.solution,
249                prev_coin_id: info.prev_coin_id,
250                this_coin_info: self.coin,
251                next_coin_proof: info.next_coin_proof,
252                extra_delta: info.extra_delta,
253                prev_subtotal: info.prev_subtotal,
254            },
255        )?;
256
257        ctx.spend(self.coin, spend)?;
258
259        Ok(())
260    }
261
262    /// Creates a [`LineageProof`] for which would be valid for any children created by this [`Cat`].
263    pub fn child_lineage_proof(&self) -> LineageProof {
264        LineageProof {
265            parent_parent_coin_info: self.coin.parent_coin_info,
266            parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
267            parent_amount: self.coin.amount,
268        }
269    }
270
271    /// Creates a new [`Cat`] that represents a child of this one.
272    /// The child will have the same revocation layer (or lack thereof) as the current [`Cat`].
273    ///
274    /// If you need to construct a child without the revocation layer, use [`Cat::unrevocable_child`].
275    pub fn child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self {
276        self.child_with(
277            CatInfo {
278                p2_puzzle_hash,
279                ..self.info
280            },
281            amount,
282        )
283    }
284
285    /// Creates a new [`Cat`] that represents a child of this one.
286    /// The child will not have a revocation layer.
287    ///
288    /// If you need to construct a child with the same revocation layer, use [`Cat::child`].
289    pub fn unrevocable_child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self {
290        self.child_with(
291            CatInfo {
292                p2_puzzle_hash,
293                hidden_puzzle_hash: None,
294                ..self.info
295            },
296            amount,
297        )
298    }
299
300    /// Creates a new [`Cat`] that represents a child of this one.
301    ///
302    /// You can specify the [`CatInfo`] to use for the child manually.
303    /// In most cases, you will want to use [`Cat::child`] or [`Cat::unrevocable_child`] instead.
304    pub fn child_with(&self, info: CatInfo, amount: u64) -> Self {
305        Self {
306            coin: Coin::new(self.coin.coin_id(), info.puzzle_hash().into(), amount),
307            lineage_proof: Some(self.child_lineage_proof()),
308            info,
309        }
310    }
311}
312
313impl Cat {
314    /// Parses the children of a [`Cat`] from the parent coin spend.
315    ///
316    /// This can be used to construct a valid spendable [`Cat`] for a hinted coin.
317    /// You simply need to look up the parent coin's spend, parse the children, and
318    /// find the one that matches the hinted coin.
319    ///
320    /// There is special handling for the revocation layer.
321    /// See [`Cat::child_from_p2_create_coin`] for more details.
322    pub fn parse_children(
323        allocator: &mut Allocator,
324        parent_coin: Coin,
325        parent_puzzle: Puzzle,
326        parent_solution: NodePtr,
327    ) -> Result<Option<Vec<Self>>, DriverError>
328    where
329        Self: Sized,
330    {
331        let Some(parent_layer) = CatLayer::<Puzzle>::parse_puzzle(allocator, parent_puzzle)? else {
332            return Ok(None);
333        };
334        let parent_solution = CatLayer::<Puzzle>::parse_solution(allocator, parent_solution)?;
335
336        let mut hidden_puzzle_hash = None;
337        let mut inner_spend = Spend::new(
338            parent_layer.inner_puzzle.ptr(),
339            parent_solution.inner_puzzle_solution,
340        );
341        let mut revoke = false;
342
343        if let Some(revocation_layer) =
344            RevocationLayer::parse_puzzle(allocator, parent_layer.inner_puzzle)?
345        {
346            hidden_puzzle_hash = Some(revocation_layer.hidden_puzzle_hash);
347
348            let revocation_solution =
349                RevocationLayer::parse_solution(allocator, parent_solution.inner_puzzle_solution)?;
350
351            inner_spend = Spend::new(revocation_solution.puzzle, revocation_solution.solution);
352            revoke = revocation_solution.hidden;
353        }
354
355        let cat = Cat::new(
356            parent_coin,
357            parent_solution.lineage_proof,
358            CatInfo::new(
359                parent_layer.asset_id,
360                hidden_puzzle_hash,
361                tree_hash(allocator, inner_spend.puzzle).into(),
362            ),
363        );
364
365        let output = run_puzzle(allocator, inner_spend.puzzle, inner_spend.solution)?;
366        let conditions = Vec::<Condition>::from_clvm(allocator, output)?;
367
368        let outputs = conditions
369            .into_iter()
370            .filter_map(Condition::into_create_coin)
371            .map(|create_coin| cat.child_from_p2_create_coin(allocator, create_coin, revoke))
372            .collect();
373
374        Ok(Some(outputs))
375    }
376
377    /// Creates a new [`Cat`] that reflects the create coin condition in the p2 spend's conditions.
378    ///
379    /// There is special handling for the revocation layer:
380    /// 1. If there is no revocation layer for the parent, the child will not have one either.
381    /// 2. If the parent was not revoked, the child will have the same revocation layer.
382    /// 3. If the parent was revoked, the child will not have a revocation layer.
383    /// 4. If the parent was revoked, and the child was hinted (and wrapped with the revocation layer), it will detect it.
384    pub fn child_from_p2_create_coin(
385        &self,
386        allocator: &Allocator,
387        create_coin: CreateCoin<NodePtr>,
388        revoke: bool,
389    ) -> Self {
390        // Child with the same hidden puzzle hash as the parent
391        let child = self.child(create_coin.puzzle_hash, create_coin.amount);
392
393        // If the parent is not revocable, we don't need to add a revocation layer
394        let Some(hidden_puzzle_hash) = self.info.hidden_puzzle_hash else {
395            return child;
396        };
397
398        // If we're not doing a revocation spend, we know it's wrapped in the same revocation layer
399        if !revoke {
400            return child;
401        }
402
403        // Child without a hidden puzzle hash but with the create coin puzzle hash as the p2 puzzle hash
404        let unrevocable_child = self.unrevocable_child(create_coin.puzzle_hash, create_coin.amount);
405
406        // If the hint is missing, just assume the child doesn't have a hidden puzzle hash
407        let Memos::Some(memos) = create_coin.memos else {
408            return unrevocable_child;
409        };
410
411        let Some((hint, _)) = <(Bytes32, NodePtr)>::from_clvm(allocator, memos).ok() else {
412            return unrevocable_child;
413        };
414
415        // If the hint wrapped in the revocation layer of the parent matches the create coin's puzzle hash,
416        // then we know that the hint is the p2 puzzle hash and the child has the same revocation layer as the parent
417        if hint
418            == RevocationLayer::new(hidden_puzzle_hash, hint)
419                .tree_hash()
420                .into()
421        {
422            return self.child(hint, create_coin.amount);
423        }
424
425        // Otherwise, we can't determine whether there is a revocation layer or not, so we will just assume it's unrevocable
426        // In practice, this should never happen while parsing a coin which is still spendable (not an ephemeral spend)
427        // If it does, a new hinting mechanism should be introduced in the future to accommodate this, but for now this is the best we can do
428        unrevocable_child
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use chia_puzzle_types::cat::EverythingWithSignatureTailArgs;
435    use chia_sdk_test::Simulator;
436    use rstest::rstest;
437
438    use crate::{SpendWithConditions, StandardLayer};
439
440    use super::*;
441
442    #[test]
443    fn test_single_issuance_cat() -> anyhow::Result<()> {
444        let mut sim = Simulator::new();
445        let ctx = &mut SpendContext::new();
446
447        let alice = sim.bls(1);
448        let alice_p2 = StandardLayer::new(alice.pk);
449
450        let memos = ctx.hint(alice.puzzle_hash)?;
451        let (issue_cat, cats) = Cat::issue_with_coin(
452            ctx,
453            alice.coin.coin_id(),
454            1,
455            Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
456        )?;
457        alice_p2.spend(ctx, alice.coin, issue_cat)?;
458
459        sim.spend_coins(ctx.take(), &[alice.sk])?;
460
461        let cat = cats[0];
462        assert_eq!(cat.info.p2_puzzle_hash, alice.puzzle_hash);
463        assert_eq!(
464            cat.info.asset_id,
465            GenesisByCoinIdTailArgs::curry_tree_hash(alice.coin.coin_id()).into()
466        );
467        assert!(sim.coin_state(cat.coin.coin_id()).is_some());
468
469        Ok(())
470    }
471
472    #[test]
473    fn test_multi_issuance_cat() -> anyhow::Result<()> {
474        let mut sim = Simulator::new();
475        let ctx = &mut SpendContext::new();
476
477        let alice = sim.bls(1);
478        let alice_p2 = StandardLayer::new(alice.pk);
479
480        let memos = ctx.hint(alice.puzzle_hash)?;
481        let (issue_cat, cats) = Cat::issue_with_key(
482            ctx,
483            alice.coin.coin_id(),
484            alice.pk,
485            1,
486            Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
487        )?;
488        alice_p2.spend(ctx, alice.coin, issue_cat)?;
489        sim.spend_coins(ctx.take(), &[alice.sk])?;
490
491        let cat = cats[0];
492        assert_eq!(cat.info.p2_puzzle_hash, alice.puzzle_hash);
493        assert_eq!(
494            cat.info.asset_id,
495            EverythingWithSignatureTailArgs::curry_tree_hash(alice.pk).into()
496        );
497        assert!(sim.coin_state(cat.coin.coin_id()).is_some());
498
499        Ok(())
500    }
501
502    #[test]
503    fn test_zero_cat_issuance() -> anyhow::Result<()> {
504        let mut sim = Simulator::new();
505        let ctx = &mut SpendContext::new();
506
507        let alice = sim.bls(0);
508        let alice_p2 = StandardLayer::new(alice.pk);
509
510        let memos = ctx.hint(alice.puzzle_hash)?;
511        let (issue_cat, cats) = Cat::issue_with_coin(
512            ctx,
513            alice.coin.coin_id(),
514            0,
515            Conditions::new().create_coin(alice.puzzle_hash, 0, memos),
516        )?;
517        alice_p2.spend(ctx, alice.coin, issue_cat)?;
518
519        sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
520
521        let cat = cats[0];
522        assert_eq!(cat.info.p2_puzzle_hash, alice.puzzle_hash);
523        assert_eq!(
524            cat.info.asset_id,
525            GenesisByCoinIdTailArgs::curry_tree_hash(alice.coin.coin_id()).into()
526        );
527        assert!(sim.coin_state(cat.coin.coin_id()).is_some());
528
529        let cat_spend = CatSpend::new(
530            cat,
531            alice_p2.spend_with_conditions(
532                ctx,
533                Conditions::new().create_coin(alice.puzzle_hash, 0, memos),
534            )?,
535        );
536        Cat::spend_all(ctx, &[cat_spend])?;
537        sim.spend_coins(ctx.take(), &[alice.sk])?;
538
539        Ok(())
540    }
541
542    #[test]
543    fn test_missing_cat_issuance_output() -> anyhow::Result<()> {
544        let mut sim = Simulator::new();
545        let ctx = &mut SpendContext::new();
546
547        let alice = sim.bls(1);
548        let alice_p2 = StandardLayer::new(alice.pk);
549
550        let (issue_cat, _cats) =
551            Cat::issue_with_coin(ctx, alice.coin.coin_id(), 1, Conditions::new())?;
552        alice_p2.spend(ctx, alice.coin, issue_cat)?;
553
554        assert_eq!(
555            sim.spend_coins(ctx.take(), &[alice.sk])
556                .unwrap_err()
557                .to_string(),
558            "Signer error: Eval error: Error at NodePtr(SmallAtom, 0): clvm raise"
559        );
560
561        Ok(())
562    }
563
564    #[test]
565    fn test_exceeded_cat_issuance_output() -> anyhow::Result<()> {
566        let mut sim = Simulator::new();
567        let ctx = &mut SpendContext::new();
568
569        let alice = sim.bls(2);
570        let alice_p2 = StandardLayer::new(alice.pk);
571
572        let memos = ctx.hint(alice.puzzle_hash)?;
573        let (issue_cat, _cats) = Cat::issue_with_coin(
574            ctx,
575            alice.coin.coin_id(),
576            1,
577            Conditions::new().create_coin(alice.puzzle_hash, 2, memos),
578        )?;
579        alice_p2.spend(ctx, alice.coin, issue_cat)?;
580
581        assert_eq!(
582            sim.spend_coins(ctx.take(), &[alice.sk])
583                .unwrap_err()
584                .to_string(),
585            "Signer error: Eval error: Error at NodePtr(SmallAtom, 0): clvm raise"
586        );
587
588        Ok(())
589    }
590
591    #[rstest]
592    #[case(1)]
593    #[case(2)]
594    #[case(3)]
595    #[case(10)]
596    fn test_cat_spends(#[case] coins: usize) -> anyhow::Result<()> {
597        let mut sim = Simulator::new();
598        let ctx = &mut SpendContext::new();
599
600        // All of the amounts are different to prevent coin id collisions.
601        let mut amounts = Vec::with_capacity(coins);
602
603        for amount in 0..coins {
604            amounts.push(amount as u64);
605        }
606
607        // Create the coin with the sum of all the amounts we need to issue.
608        let sum = amounts.iter().sum::<u64>();
609
610        let alice = sim.bls(sum);
611        let alice_p2 = StandardLayer::new(alice.pk);
612
613        // Issue the CAT coins with those amounts.
614        let mut conditions = Conditions::new();
615
616        let memos = ctx.hint(alice.puzzle_hash)?;
617        for &amount in &amounts {
618            conditions = conditions.create_coin(alice.puzzle_hash, amount, memos);
619        }
620
621        let (issue_cat, mut cats) =
622            Cat::issue_with_coin(ctx, alice.coin.coin_id(), sum, conditions)?;
623        alice_p2.spend(ctx, alice.coin, issue_cat)?;
624
625        sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
626
627        // Spend the CAT coins a few times.
628        for _ in 0..3 {
629            let cat_spends: Vec<CatSpend> = cats
630                .iter()
631                .map(|cat| {
632                    Ok(CatSpend::new(
633                        *cat,
634                        alice_p2.spend_with_conditions(
635                            ctx,
636                            Conditions::new().create_coin(
637                                alice.puzzle_hash,
638                                cat.coin.amount,
639                                memos,
640                            ),
641                        )?,
642                    ))
643                })
644                .collect::<anyhow::Result<_>>()?;
645
646            cats = Cat::spend_all(ctx, &cat_spends)?;
647            sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
648        }
649
650        Ok(())
651    }
652
653    #[test]
654    fn test_different_cat_p2_puzzles() -> anyhow::Result<()> {
655        let mut sim = Simulator::new();
656        let ctx = &mut SpendContext::new();
657
658        let alice = sim.bls(2);
659        let alice_p2 = StandardLayer::new(alice.pk);
660
661        // This will just return the solution verbatim.
662        let custom_p2 = ctx.alloc(&1)?;
663        let custom_p2_puzzle_hash = ctx.tree_hash(custom_p2).into();
664
665        let memos = ctx.hint(alice.puzzle_hash)?;
666        let custom_memos = ctx.hint(custom_p2_puzzle_hash)?;
667        let (issue_cat, cats) = Cat::issue_with_coin(
668            ctx,
669            alice.coin.coin_id(),
670            2,
671            Conditions::new()
672                .create_coin(alice.puzzle_hash, 1, memos)
673                .create_coin(custom_p2_puzzle_hash, 1, custom_memos),
674        )?;
675        alice_p2.spend(ctx, alice.coin, issue_cat)?;
676        sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
677
678        let spends = [
679            CatSpend::new(
680                cats[0],
681                alice_p2.spend_with_conditions(
682                    ctx,
683                    Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
684                )?,
685            ),
686            CatSpend::new(
687                cats[1],
688                Spend::new(
689                    custom_p2,
690                    ctx.alloc(&[CreateCoin::new(custom_p2_puzzle_hash, 1, custom_memos)])?,
691                ),
692            ),
693        ];
694
695        Cat::spend_all(ctx, &spends)?;
696        sim.spend_coins(ctx.take(), &[alice.sk])?;
697
698        Ok(())
699    }
700
701    #[test]
702    fn test_cat_melt() -> anyhow::Result<()> {
703        let mut sim = Simulator::new();
704        let ctx = &mut SpendContext::new();
705
706        let alice = sim.bls(10000);
707        let alice_p2 = StandardLayer::new(alice.pk);
708
709        let memos = ctx.hint(alice.puzzle_hash)?;
710        let conditions = Conditions::new().create_coin(alice.puzzle_hash, 10000, memos);
711        let (issue_cat, cats) =
712            Cat::issue_with_key(ctx, alice.coin.coin_id(), alice.pk, 10000, conditions)?;
713        alice_p2.spend(ctx, alice.coin, issue_cat)?;
714
715        let tail = ctx.curry(EverythingWithSignatureTailArgs::new(alice.pk))?;
716
717        let cat_spend = CatSpend::new(
718            cats[0],
719            alice_p2.spend_with_conditions(
720                ctx,
721                Conditions::new()
722                    .create_coin(alice.puzzle_hash, 7000, memos)
723                    .run_cat_tail(tail, NodePtr::NIL),
724            )?,
725        );
726
727        Cat::spend_all(ctx, &[cat_spend])?;
728
729        sim.spend_coins(ctx.take(), &[alice.sk])?;
730
731        Ok(())
732    }
733}