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