1use chia_protocol::{Bytes32, Coin};
2use chia_sdk_types::{
3 conditions::{AssertBeforeSecondsAbsolute, AssertSecondsAbsolute, CreateCoin, Memos},
4 puzzles::{AugmentedConditionArgs, AugmentedConditionSolution, P2OneOfManySolution},
5 Conditions, MerkleTree, Mod,
6};
7use clvm_traits::{clvm_list, clvm_quote, match_list, FromClvm};
8use clvm_utils::{ToTreeHash, TreeHash};
9use clvmr::{Allocator, NodePtr};
10
11use crate::{DriverError, Layer, P2OneOfManyLayer, Spend, SpendContext, SpendWithConditions};
12
13pub type PushThroughPath = (
14 u8,
15 match_list!(AssertSecondsAbsolute, CreateCoin<[Bytes32; 1]>),
16);
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct ClawbackV2 {
20 pub sender_puzzle_hash: Bytes32,
21 pub receiver_puzzle_hash: Bytes32,
22 pub seconds: u64,
23 pub amount: u64,
24 pub hinted: bool,
25}
26
27impl ClawbackV2 {
28 pub fn new(
29 sender_puzzle_hash: Bytes32,
30 receiver_puzzle_hash: Bytes32,
31 seconds: u64,
32 amount: u64,
33 hinted: bool,
34 ) -> Self {
35 Self {
36 sender_puzzle_hash,
37 receiver_puzzle_hash,
38 seconds,
39 amount,
40 hinted,
41 }
42 }
43
44 pub fn from_memo(
45 allocator: &Allocator,
46 memo: NodePtr,
47 receiver_puzzle_hash: Bytes32,
48 amount: u64,
49 hinted: bool,
50 expected_puzzle_hash: Bytes32,
51 ) -> Option<Self> {
52 let (sender_puzzle_hash, (seconds, ())) =
53 <(Bytes32, (u64, ()))>::from_clvm(allocator, memo).ok()?;
54
55 let clawback = Self {
56 sender_puzzle_hash,
57 receiver_puzzle_hash,
58 seconds,
59 amount,
60 hinted,
61 };
62
63 if clawback.tree_hash() != expected_puzzle_hash.into() {
64 return None;
65 }
66
67 Some(clawback)
68 }
69
70 pub fn memo(&self) -> (Bytes32, (u64, ())) {
71 (self.sender_puzzle_hash, (self.seconds, ()))
72 }
73
74 pub fn merkle_tree(&self) -> MerkleTree {
75 MerkleTree::new(&[
76 self.sender_path_hash(),
77 self.receiver_path_hash(),
78 self.push_through_path_hash(),
79 ])
80 }
81
82 pub fn sender_path_hash(&self) -> Bytes32 {
83 AugmentedConditionArgs::<TreeHash, TreeHash>::new(
84 AssertBeforeSecondsAbsolute::new(self.seconds).into(),
85 self.sender_puzzle_hash.into(),
86 )
87 .curry_tree_hash()
88 .into()
89 }
90
91 pub fn receiver_path_hash(&self) -> Bytes32 {
92 AugmentedConditionArgs::<TreeHash, TreeHash>::new(
93 AssertSecondsAbsolute::new(self.seconds).into(),
94 self.receiver_puzzle_hash.into(),
95 )
96 .curry_tree_hash()
97 .into()
98 }
99
100 pub fn push_through_path(&self) -> PushThroughPath {
101 clvm_quote!(clvm_list!(
102 AssertSecondsAbsolute::new(self.seconds),
103 CreateCoin::new(
104 self.receiver_puzzle_hash,
105 self.amount,
106 if self.hinted {
107 Memos::Some([self.receiver_puzzle_hash])
108 } else {
109 Memos::None
110 }
111 )
112 ))
113 }
114
115 pub fn push_through_path_hash(&self) -> Bytes32 {
116 self.push_through_path().tree_hash().into()
117 }
118
119 pub fn into_1_of_n(&self) -> P2OneOfManyLayer {
120 P2OneOfManyLayer::new(self.merkle_tree().root())
121 }
122
123 pub fn sender_spend(&self, ctx: &mut SpendContext, spend: Spend) -> Result<Spend, DriverError> {
124 let merkle_tree = self.merkle_tree();
125
126 let puzzle_hash = self.sender_path_hash();
127 let merkle_proof = merkle_tree
128 .proof(puzzle_hash)
129 .ok_or(DriverError::InvalidMerkleProof)?;
130
131 let puzzle = ctx.curry(AugmentedConditionArgs::<NodePtr, NodePtr>::new(
132 AssertBeforeSecondsAbsolute::new(self.seconds).into(),
133 spend.puzzle,
134 ))?;
135
136 let solution = ctx.alloc(&AugmentedConditionSolution::new(spend.solution))?;
137
138 P2OneOfManyLayer::new(merkle_tree.root()).construct_spend(
139 ctx,
140 P2OneOfManySolution::new(merkle_proof, puzzle, solution),
141 )
142 }
143
144 pub fn receiver_spend(
145 &self,
146 ctx: &mut SpendContext,
147 spend: Spend,
148 ) -> Result<Spend, DriverError> {
149 let merkle_tree = self.merkle_tree();
150
151 let puzzle_hash = self.receiver_path_hash();
152 let merkle_proof = merkle_tree
153 .proof(puzzle_hash)
154 .ok_or(DriverError::InvalidMerkleProof)?;
155
156 let puzzle = ctx.curry(AugmentedConditionArgs::<NodePtr, NodePtr>::new(
157 AssertSecondsAbsolute::new(self.seconds).into(),
158 spend.puzzle,
159 ))?;
160
161 let solution = ctx.alloc(&AugmentedConditionSolution::new(spend.solution))?;
162
163 P2OneOfManyLayer::new(merkle_tree.root()).construct_spend(
164 ctx,
165 P2OneOfManySolution::new(merkle_proof, puzzle, solution),
166 )
167 }
168
169 pub fn push_through_spend(&self, ctx: &mut SpendContext) -> Result<Spend, DriverError> {
170 let merkle_tree = self.merkle_tree();
171
172 let puzzle_hash = self.push_through_path_hash();
173 let merkle_proof = merkle_tree
174 .proof(puzzle_hash)
175 .ok_or(DriverError::InvalidMerkleProof)?;
176
177 let puzzle = ctx.alloc(&self.push_through_path())?;
178
179 P2OneOfManyLayer::new(merkle_tree.root()).construct_spend(
180 ctx,
181 P2OneOfManySolution::new(merkle_proof, puzzle, NodePtr::NIL),
182 )
183 }
184
185 pub fn recover_spend<I>(
186 &self,
187 ctx: &mut SpendContext,
188 inner: &I,
189 conditions: Conditions,
190 ) -> Result<Spend, DriverError>
191 where
192 I: SpendWithConditions,
193 {
194 let hint = ctx.hint(self.sender_puzzle_hash)?;
195
196 let inner_spend = inner.spend_with_conditions(
197 ctx,
198 conditions.create_coin(
199 self.sender_puzzle_hash,
200 self.amount,
201 if self.hinted { hint } else { Memos::None },
202 ),
203 )?;
204
205 self.sender_spend(ctx, inner_spend)
206 }
207
208 pub fn recover_coin_spend<I>(
209 &self,
210 ctx: &mut SpendContext,
211 coin: Coin,
212 inner: &I,
213 conditions: Conditions,
214 ) -> Result<(), DriverError>
215 where
216 I: SpendWithConditions,
217 {
218 let spend = self.recover_spend(ctx, inner, conditions)?;
219 ctx.spend(coin, spend)
220 }
221
222 pub fn force_spend<I>(
223 &self,
224 ctx: &mut SpendContext,
225 inner: &I,
226 conditions: Conditions,
227 ) -> Result<Spend, DriverError>
228 where
229 I: SpendWithConditions,
230 {
231 let hint = ctx.hint(self.receiver_puzzle_hash)?;
232
233 let inner_spend = inner.spend_with_conditions(
234 ctx,
235 conditions.create_coin(
236 self.receiver_puzzle_hash,
237 self.amount,
238 if self.hinted { hint } else { Memos::None },
239 ),
240 )?;
241
242 self.sender_spend(ctx, inner_spend)
243 }
244
245 pub fn force_coin_spend<I>(
246 &self,
247 ctx: &mut SpendContext,
248 coin: Coin,
249 inner: &I,
250 conditions: Conditions,
251 ) -> Result<(), DriverError>
252 where
253 I: SpendWithConditions,
254 {
255 let spend = self.force_spend(ctx, inner, conditions)?;
256 ctx.spend(coin, spend)
257 }
258
259 pub fn finish_spend<I>(
260 &self,
261 ctx: &mut SpendContext,
262 inner: &I,
263 conditions: Conditions,
264 ) -> Result<Spend, DriverError>
265 where
266 I: SpendWithConditions,
267 {
268 let hint = ctx.hint(self.receiver_puzzle_hash)?;
269
270 let inner_spend = inner.spend_with_conditions(
271 ctx,
272 conditions.create_coin(
273 self.receiver_puzzle_hash,
274 self.amount,
275 if self.hinted { hint } else { Memos::None },
276 ),
277 )?;
278
279 self.receiver_spend(ctx, inner_spend)
280 }
281
282 pub fn finish_coin_spend<I>(
283 &self,
284 ctx: &mut SpendContext,
285 coin: Coin,
286 inner: &I,
287 conditions: Conditions,
288 ) -> Result<(), DriverError>
289 where
290 I: SpendWithConditions,
291 {
292 let spend = self.finish_spend(ctx, inner, conditions)?;
293 ctx.spend(coin, spend)
294 }
295
296 pub fn push_through_coin_spend(
297 &self,
298 ctx: &mut SpendContext,
299 coin: Coin,
300 ) -> Result<(), DriverError> {
301 let spend = self.push_through_spend(ctx)?;
302 ctx.spend(coin, spend)
303 }
304}
305
306impl ToTreeHash for ClawbackV2 {
307 fn tree_hash(&self) -> TreeHash {
308 self.into_1_of_n().tree_hash()
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use chia_protocol::Coin;
315 use chia_sdk_test::{expect_spend, Simulator};
316 use clvm_traits::{clvm_list, ToClvm};
317 use rstest::rstest;
318
319 use crate::{Cat, CatSpend, SpendWithConditions, StandardLayer};
320
321 use super::*;
322
323 #[rstest]
324 fn test_clawback_memo(#[values(false, true)] hinted: bool) -> anyhow::Result<()> {
325 let mut allocator = Allocator::new();
326
327 let clawback =
328 ClawbackV2::new(Bytes32::new([1; 32]), Bytes32::new([2; 32]), 100, 1, hinted);
329 let memo = clawback.memo().to_clvm(&mut allocator)?;
330
331 let roundtrip = ClawbackV2::from_memo(
332 &allocator,
333 memo,
334 Bytes32::new([2; 32]),
335 1,
336 hinted,
337 clawback.tree_hash().into(),
338 );
339 assert_eq!(roundtrip, Some(clawback));
340
341 Ok(())
342 }
343
344 #[rstest]
345 fn test_clawback_v2_recover_xch(
346 #[values(false, true)] hinted: bool,
347 #[values(false, true)] after_expiration: bool,
348 ) -> anyhow::Result<()> {
349 let mut sim = Simulator::new();
350 let mut ctx = SpendContext::new();
351
352 if after_expiration {
353 sim.set_next_timestamp(100)?;
354 }
355
356 let alice = sim.bls(1);
357 let p2_alice = StandardLayer::new(alice.pk);
358
359 let bob = sim.bls(0);
360
361 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, hinted);
362 let clawback_puzzle_hash = clawback.tree_hash().into();
363 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
364
365 p2_alice.spend(
366 &mut ctx,
367 alice.coin,
368 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
369 )?;
370 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
371
372 sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
373
374 clawback.recover_coin_spend(&mut ctx, clawback_coin, &p2_alice, Conditions::new())?;
375
376 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
377
378 if !after_expiration {
379 assert!(sim
380 .coin_state(Coin::new(clawback_coin.coin_id(), alice.puzzle_hash, 1).coin_id())
381 .is_some());
382 }
383
384 Ok(())
385 }
386
387 #[rstest]
388 fn test_clawback_v2_force_xch(
389 #[values(false, true)] hinted: bool,
390 #[values(false, true)] after_expiration: bool,
391 ) -> anyhow::Result<()> {
392 let mut sim = Simulator::new();
393 let mut ctx = SpendContext::new();
394
395 if after_expiration {
396 sim.set_next_timestamp(100)?;
397 }
398
399 let alice = sim.bls(1);
400 let p2_alice = StandardLayer::new(alice.pk);
401
402 let bob = sim.bls(0);
403
404 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, hinted);
405 let clawback_puzzle_hash = clawback.tree_hash().into();
406 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
407
408 p2_alice.spend(
409 &mut ctx,
410 alice.coin,
411 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
412 )?;
413 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
414
415 sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
416
417 clawback.force_coin_spend(&mut ctx, clawback_coin, &p2_alice, Conditions::new())?;
418
419 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
420
421 if !after_expiration {
422 assert!(sim
423 .coin_state(Coin::new(clawback_coin.coin_id(), bob.puzzle_hash, 1).coin_id())
424 .is_some());
425 }
426
427 Ok(())
428 }
429
430 #[rstest]
431 fn test_clawback_v2_finish_xch(
432 #[values(false, true)] hinted: bool,
433 #[values(false, true)] after_expiration: bool,
434 ) -> anyhow::Result<()> {
435 let mut sim = Simulator::new();
436 let mut ctx = SpendContext::new();
437
438 if after_expiration {
439 sim.set_next_timestamp(100)?;
440 }
441
442 let alice = sim.bls(1);
443 let p2_alice = StandardLayer::new(alice.pk);
444
445 let bob = sim.bls(0);
446 let p2_bob = StandardLayer::new(bob.pk);
447
448 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, hinted);
449 let clawback_puzzle_hash = clawback.tree_hash().into();
450 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
451
452 p2_alice.spend(
453 &mut ctx,
454 alice.coin,
455 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
456 )?;
457 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
458
459 sim.spend_coins(ctx.take(), &[alice.sk])?;
460
461 clawback.finish_coin_spend(&mut ctx, clawback_coin, &p2_bob, Conditions::new())?;
462
463 expect_spend(sim.spend_coins(ctx.take(), &[bob.sk]), after_expiration);
464
465 if after_expiration {
466 assert!(sim
467 .coin_state(Coin::new(clawback_coin.coin_id(), bob.puzzle_hash, 1).coin_id())
468 .is_some());
469 }
470
471 Ok(())
472 }
473
474 #[rstest]
475 fn test_clawback_v2_push_through_xch(
476 #[values(false, true)] hinted: bool,
477 #[values(false, true)] after_expiration: bool,
478 ) -> anyhow::Result<()> {
479 let mut sim = Simulator::new();
480 let mut ctx = SpendContext::new();
481
482 if after_expiration {
483 sim.set_next_timestamp(100)?;
484 }
485
486 let alice = sim.bls(1);
487 let p2_alice = StandardLayer::new(alice.pk);
488
489 let bob = sim.bls(0);
490
491 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, hinted);
492 let clawback_puzzle_hash = clawback.tree_hash().into();
493 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
494
495 p2_alice.spend(
496 &mut ctx,
497 alice.coin,
498 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
499 )?;
500 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
501
502 sim.spend_coins(ctx.take(), &[alice.sk])?;
503
504 clawback.push_through_coin_spend(&mut ctx, clawback_coin)?;
505
506 expect_spend(sim.spend_coins(ctx.take(), &[bob.sk]), after_expiration);
507
508 if after_expiration {
509 assert!(sim
510 .coin_state(Coin::new(clawback_coin.coin_id(), bob.puzzle_hash, 1).coin_id())
511 .is_some());
512 }
513
514 Ok(())
515 }
516
517 #[rstest]
518 fn test_clawback_v2_recover_cat(
519 #[values(false, true)] after_expiration: bool,
520 ) -> anyhow::Result<()> {
521 let mut sim = Simulator::new();
522 let mut ctx = SpendContext::new();
523
524 if after_expiration {
525 sim.set_next_timestamp(100)?;
526 }
527
528 let alice = sim.bls(1);
529 let p2_alice = StandardLayer::new(alice.pk);
530
531 let bob = sim.bls(0);
532
533 let memos = ctx.hint(alice.puzzle_hash)?;
534 let (issue_cat, cats) = Cat::issue_with_coin(
535 &mut ctx,
536 alice.coin.coin_id(),
537 1,
538 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
539 )?;
540 let cat = cats[0];
541 p2_alice.spend(&mut ctx, alice.coin, issue_cat)?;
542
543 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
544 let clawback_puzzle_hash = clawback.tree_hash().into();
545 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
546
547 let inner_spend = p2_alice.spend_with_conditions(
548 &mut ctx,
549 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
550 )?;
551 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
552
553 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
554
555 sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
556
557 let clawback_spend = clawback.recover_spend(&mut ctx, &p2_alice, Conditions::new())?;
558 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
559
560 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
561
562 if !after_expiration {
563 assert!(sim
564 .coin_state(clawback_cat.child(alice.puzzle_hash, 1).coin.coin_id())
565 .is_some());
566 }
567
568 Ok(())
569 }
570
571 #[rstest]
572 fn test_clawback_v2_force_cat(
573 #[values(false, true)] after_expiration: bool,
574 ) -> anyhow::Result<()> {
575 let mut sim = Simulator::new();
576 let mut ctx = SpendContext::new();
577
578 if after_expiration {
579 sim.set_next_timestamp(100)?;
580 }
581
582 let alice = sim.bls(1);
583 let p2_alice = StandardLayer::new(alice.pk);
584
585 let bob = sim.bls(0);
586
587 let memos = ctx.hint(alice.puzzle_hash)?;
588 let (issue_cat, cats) = Cat::issue_with_coin(
589 &mut ctx,
590 alice.coin.coin_id(),
591 1,
592 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
593 )?;
594 let cat = cats[0];
595 p2_alice.spend(&mut ctx, alice.coin, issue_cat)?;
596
597 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
598 let clawback_puzzle_hash = clawback.tree_hash().into();
599 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
600
601 let inner_spend = p2_alice.spend_with_conditions(
602 &mut ctx,
603 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
604 )?;
605 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
606
607 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
608
609 sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
610
611 let clawback_spend = clawback.force_spend(&mut ctx, &p2_alice, Conditions::new())?;
612 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
613
614 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
615
616 if !after_expiration {
617 assert!(sim
618 .coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
619 .is_some());
620 }
621
622 Ok(())
623 }
624
625 #[rstest]
626 fn test_clawback_v2_finish_cat(
627 #[values(false, true)] after_expiration: bool,
628 ) -> anyhow::Result<()> {
629 let mut sim = Simulator::new();
630 let mut ctx = SpendContext::new();
631
632 if after_expiration {
633 sim.set_next_timestamp(100)?;
634 }
635
636 let alice = sim.bls(1);
637 let p2_alice = StandardLayer::new(alice.pk);
638
639 let bob = sim.bls(0);
640 let p2_bob = StandardLayer::new(bob.pk);
641
642 let memos = ctx.hint(alice.puzzle_hash)?;
643 let (issue_cat, cats) = Cat::issue_with_coin(
644 &mut ctx,
645 alice.coin.coin_id(),
646 1,
647 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
648 )?;
649 let cat = cats[0];
650 p2_alice.spend(
651 &mut ctx,
652 alice.coin,
653 issue_cat.create_coin(alice.puzzle_hash, 0, Memos::None),
654 )?;
655
656 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
657 let clawback_puzzle_hash = clawback.tree_hash().into();
658 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
659
660 let inner_spend = p2_alice.spend_with_conditions(
661 &mut ctx,
662 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
663 )?;
664 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
665
666 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
667
668 sim.spend_coins(ctx.take(), &[alice.sk])?;
669
670 let clawback_spend = clawback.finish_spend(&mut ctx, &p2_bob, Conditions::new())?;
671 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
672
673 expect_spend(sim.spend_coins(ctx.take(), &[bob.sk]), after_expiration);
674
675 if after_expiration {
676 assert!(sim
677 .coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
678 .is_some());
679 }
680
681 Ok(())
682 }
683
684 #[rstest]
685 fn test_clawback_v2_push_through_cat(
686 #[values(false, true)] after_expiration: bool,
687 ) -> anyhow::Result<()> {
688 let mut sim = Simulator::new();
689 let mut ctx = SpendContext::new();
690
691 if after_expiration {
692 sim.set_next_timestamp(100)?;
693 }
694
695 let alice = sim.bls(1);
696 let p2_alice = StandardLayer::new(alice.pk);
697
698 let bob = sim.bls(0);
699
700 let memos = ctx.hint(alice.puzzle_hash)?;
701 let (issue_cat, cats) = Cat::issue_with_coin(
702 &mut ctx,
703 alice.coin.coin_id(),
704 1,
705 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
706 )?;
707 let cat = cats[0];
708 p2_alice.spend(
709 &mut ctx,
710 alice.coin,
711 issue_cat.create_coin(alice.puzzle_hash, 0, Memos::None),
712 )?;
713
714 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
715 let clawback_puzzle_hash = clawback.tree_hash().into();
716 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
717
718 let inner_spend = p2_alice.spend_with_conditions(
719 &mut ctx,
720 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
721 )?;
722 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
723
724 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
725
726 sim.spend_coins(ctx.take(), &[alice.sk])?;
727
728 let clawback_spend = clawback.push_through_spend(&mut ctx)?;
729 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
730
731 expect_spend(sim.spend_coins(ctx.take(), &[]), after_expiration);
732
733 if after_expiration {
734 assert!(sim
735 .coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
736 .is_some());
737 }
738
739 Ok(())
740 }
741}