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 std::slice;
315
316 use chia_protocol::Coin;
317 use chia_sdk_test::{expect_spend, Simulator};
318 use clvm_traits::{clvm_list, ToClvm};
319 use rstest::rstest;
320
321 use crate::{Cat, CatSpend, SpendWithConditions, StandardLayer};
322
323 use super::*;
324
325 #[rstest]
326 fn test_clawback_memo(#[values(false, true)] hinted: bool) -> anyhow::Result<()> {
327 let mut allocator = Allocator::new();
328
329 let clawback =
330 ClawbackV2::new(Bytes32::new([1; 32]), Bytes32::new([2; 32]), 100, 1, hinted);
331 let memo = clawback.memo().to_clvm(&mut allocator)?;
332
333 let roundtrip = ClawbackV2::from_memo(
334 &allocator,
335 memo,
336 Bytes32::new([2; 32]),
337 1,
338 hinted,
339 clawback.tree_hash().into(),
340 );
341 assert_eq!(roundtrip, Some(clawback));
342
343 Ok(())
344 }
345
346 #[rstest]
347 fn test_clawback_v2_recover_xch(
348 #[values(false, true)] hinted: bool,
349 #[values(false, true)] after_expiration: bool,
350 ) -> anyhow::Result<()> {
351 let mut sim = Simulator::new();
352 let mut ctx = SpendContext::new();
353
354 if after_expiration {
355 sim.set_next_timestamp(100)?;
356 }
357
358 let alice = sim.bls(1);
359 let p2_alice = StandardLayer::new(alice.pk);
360
361 let bob = sim.bls(0);
362
363 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, hinted);
364 let clawback_puzzle_hash = clawback.tree_hash().into();
365 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
366
367 p2_alice.spend(
368 &mut ctx,
369 alice.coin,
370 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
371 )?;
372 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
373
374 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
375
376 clawback.recover_coin_spend(&mut ctx, clawback_coin, &p2_alice, Conditions::new())?;
377
378 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
379
380 if !after_expiration {
381 assert!(sim
382 .coin_state(Coin::new(clawback_coin.coin_id(), alice.puzzle_hash, 1).coin_id())
383 .is_some());
384 }
385
386 Ok(())
387 }
388
389 #[rstest]
390 fn test_clawback_v2_force_xch(
391 #[values(false, true)] hinted: bool,
392 #[values(false, true)] after_expiration: bool,
393 ) -> anyhow::Result<()> {
394 let mut sim = Simulator::new();
395 let mut ctx = SpendContext::new();
396
397 if after_expiration {
398 sim.set_next_timestamp(100)?;
399 }
400
401 let alice = sim.bls(1);
402 let p2_alice = StandardLayer::new(alice.pk);
403
404 let bob = sim.bls(0);
405
406 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, hinted);
407 let clawback_puzzle_hash = clawback.tree_hash().into();
408 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
409
410 p2_alice.spend(
411 &mut ctx,
412 alice.coin,
413 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
414 )?;
415 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
416
417 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
418
419 clawback.force_coin_spend(&mut ctx, clawback_coin, &p2_alice, Conditions::new())?;
420
421 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
422
423 if !after_expiration {
424 assert!(sim
425 .coin_state(Coin::new(clawback_coin.coin_id(), bob.puzzle_hash, 1).coin_id())
426 .is_some());
427 }
428
429 Ok(())
430 }
431
432 #[rstest]
433 fn test_clawback_v2_finish_xch(
434 #[values(false, true)] hinted: bool,
435 #[values(false, true)] after_expiration: bool,
436 ) -> anyhow::Result<()> {
437 let mut sim = Simulator::new();
438 let mut ctx = SpendContext::new();
439
440 if after_expiration {
441 sim.set_next_timestamp(100)?;
442 }
443
444 let alice = sim.bls(1);
445 let p2_alice = StandardLayer::new(alice.pk);
446
447 let bob = sim.bls(0);
448 let p2_bob = StandardLayer::new(bob.pk);
449
450 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, hinted);
451 let clawback_puzzle_hash = clawback.tree_hash().into();
452 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
453
454 p2_alice.spend(
455 &mut ctx,
456 alice.coin,
457 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
458 )?;
459 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
460
461 sim.spend_coins(ctx.take(), &[alice.sk])?;
462
463 clawback.finish_coin_spend(&mut ctx, clawback_coin, &p2_bob, Conditions::new())?;
464
465 expect_spend(sim.spend_coins(ctx.take(), &[bob.sk]), after_expiration);
466
467 if after_expiration {
468 assert!(sim
469 .coin_state(Coin::new(clawback_coin.coin_id(), bob.puzzle_hash, 1).coin_id())
470 .is_some());
471 }
472
473 Ok(())
474 }
475
476 #[rstest]
477 fn test_clawback_v2_push_through_xch(
478 #[values(false, true)] hinted: bool,
479 #[values(false, true)] after_expiration: bool,
480 ) -> anyhow::Result<()> {
481 let mut sim = Simulator::new();
482 let mut ctx = SpendContext::new();
483
484 if after_expiration {
485 sim.set_next_timestamp(100)?;
486 }
487
488 let alice = sim.bls(1);
489 let p2_alice = StandardLayer::new(alice.pk);
490
491 let bob = sim.bls(0);
492
493 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, hinted);
494 let clawback_puzzle_hash = clawback.tree_hash().into();
495 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
496
497 p2_alice.spend(
498 &mut ctx,
499 alice.coin,
500 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
501 )?;
502 let clawback_coin = Coin::new(alice.coin.coin_id(), clawback_puzzle_hash, 1);
503
504 sim.spend_coins(ctx.take(), &[alice.sk])?;
505
506 clawback.push_through_coin_spend(&mut ctx, clawback_coin)?;
507
508 expect_spend(sim.spend_coins(ctx.take(), &[bob.sk]), after_expiration);
509
510 if after_expiration {
511 assert!(sim
512 .coin_state(Coin::new(clawback_coin.coin_id(), bob.puzzle_hash, 1).coin_id())
513 .is_some());
514 }
515
516 Ok(())
517 }
518
519 #[rstest]
520 fn test_clawback_v2_recover_cat(
521 #[values(false, true)] after_expiration: bool,
522 ) -> anyhow::Result<()> {
523 let mut sim = Simulator::new();
524 let mut ctx = SpendContext::new();
525
526 if after_expiration {
527 sim.set_next_timestamp(100)?;
528 }
529
530 let alice = sim.bls(1);
531 let p2_alice = StandardLayer::new(alice.pk);
532
533 let bob = sim.bls(0);
534
535 let memos = ctx.hint(alice.puzzle_hash)?;
536 let (issue_cat, cats) = Cat::issue_with_coin(
537 &mut ctx,
538 alice.coin.coin_id(),
539 1,
540 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
541 )?;
542 let cat = cats[0];
543 p2_alice.spend(&mut ctx, alice.coin, issue_cat)?;
544
545 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
546 let clawback_puzzle_hash = clawback.tree_hash().into();
547 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
548
549 let inner_spend = p2_alice.spend_with_conditions(
550 &mut ctx,
551 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
552 )?;
553 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
554
555 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
556
557 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
558
559 let clawback_spend = clawback.recover_spend(&mut ctx, &p2_alice, Conditions::new())?;
560 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
561
562 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
563
564 if !after_expiration {
565 assert!(sim
566 .coin_state(clawback_cat.child(alice.puzzle_hash, 1).coin.coin_id())
567 .is_some());
568 }
569
570 Ok(())
571 }
572
573 #[rstest]
574 fn test_clawback_v2_force_cat(
575 #[values(false, true)] after_expiration: bool,
576 ) -> anyhow::Result<()> {
577 let mut sim = Simulator::new();
578 let mut ctx = SpendContext::new();
579
580 if after_expiration {
581 sim.set_next_timestamp(100)?;
582 }
583
584 let alice = sim.bls(1);
585 let p2_alice = StandardLayer::new(alice.pk);
586
587 let bob = sim.bls(0);
588
589 let memos = ctx.hint(alice.puzzle_hash)?;
590 let (issue_cat, cats) = Cat::issue_with_coin(
591 &mut ctx,
592 alice.coin.coin_id(),
593 1,
594 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
595 )?;
596 let cat = cats[0];
597 p2_alice.spend(&mut ctx, alice.coin, issue_cat)?;
598
599 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
600 let clawback_puzzle_hash = clawback.tree_hash().into();
601 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
602
603 let inner_spend = p2_alice.spend_with_conditions(
604 &mut ctx,
605 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
606 )?;
607 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
608
609 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
610
611 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
612
613 let clawback_spend = clawback.force_spend(&mut ctx, &p2_alice, Conditions::new())?;
614 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
615
616 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
617
618 if !after_expiration {
619 assert!(sim
620 .coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
621 .is_some());
622 }
623
624 Ok(())
625 }
626
627 #[rstest]
628 fn test_clawback_v2_finish_cat(
629 #[values(false, true)] after_expiration: bool,
630 ) -> anyhow::Result<()> {
631 let mut sim = Simulator::new();
632 let mut ctx = SpendContext::new();
633
634 if after_expiration {
635 sim.set_next_timestamp(100)?;
636 }
637
638 let alice = sim.bls(1);
639 let p2_alice = StandardLayer::new(alice.pk);
640
641 let bob = sim.bls(0);
642 let p2_bob = StandardLayer::new(bob.pk);
643
644 let memos = ctx.hint(alice.puzzle_hash)?;
645 let (issue_cat, cats) = Cat::issue_with_coin(
646 &mut ctx,
647 alice.coin.coin_id(),
648 1,
649 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
650 )?;
651 let cat = cats[0];
652 p2_alice.spend(
653 &mut ctx,
654 alice.coin,
655 issue_cat.create_coin(alice.puzzle_hash, 0, Memos::None),
656 )?;
657
658 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
659 let clawback_puzzle_hash = clawback.tree_hash().into();
660 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
661
662 let inner_spend = p2_alice.spend_with_conditions(
663 &mut ctx,
664 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
665 )?;
666 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
667
668 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
669
670 sim.spend_coins(ctx.take(), &[alice.sk])?;
671
672 let clawback_spend = clawback.finish_spend(&mut ctx, &p2_bob, Conditions::new())?;
673 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
674
675 expect_spend(sim.spend_coins(ctx.take(), &[bob.sk]), after_expiration);
676
677 if after_expiration {
678 assert!(sim
679 .coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
680 .is_some());
681 }
682
683 Ok(())
684 }
685
686 #[rstest]
687 fn test_clawback_v2_push_through_cat(
688 #[values(false, true)] after_expiration: bool,
689 ) -> anyhow::Result<()> {
690 let mut sim = Simulator::new();
691 let mut ctx = SpendContext::new();
692
693 if after_expiration {
694 sim.set_next_timestamp(100)?;
695 }
696
697 let alice = sim.bls(1);
698 let p2_alice = StandardLayer::new(alice.pk);
699
700 let bob = sim.bls(0);
701
702 let memos = ctx.hint(alice.puzzle_hash)?;
703 let (issue_cat, cats) = Cat::issue_with_coin(
704 &mut ctx,
705 alice.coin.coin_id(),
706 1,
707 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
708 )?;
709 let cat = cats[0];
710 p2_alice.spend(
711 &mut ctx,
712 alice.coin,
713 issue_cat.create_coin(alice.puzzle_hash, 0, Memos::None),
714 )?;
715
716 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
717 let clawback_puzzle_hash = clawback.tree_hash().into();
718 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
719
720 let inner_spend = p2_alice.spend_with_conditions(
721 &mut ctx,
722 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
723 )?;
724 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
725
726 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
727
728 sim.spend_coins(ctx.take(), &[alice.sk])?;
729
730 let clawback_spend = clawback.push_through_spend(&mut ctx)?;
731 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
732
733 expect_spend(sim.spend_coins(ctx.take(), &[]), after_expiration);
734
735 if after_expiration {
736 assert!(sim
737 .coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
738 .is_some());
739 }
740
741 Ok(())
742 }
743}