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, Hash)]
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::single_issuance(
541 &mut ctx,
542 alice.coin.coin_id(),
543 None,
544 1,
545 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
546 )?;
547 let cat = cats[0];
548 p2_alice.spend(&mut ctx, alice.coin, issue_cat)?;
549
550 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
551 let clawback_puzzle_hash = clawback.tree_hash().into();
552 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
553
554 let inner_spend = p2_alice.spend_with_conditions(
555 &mut ctx,
556 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
557 )?;
558 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
559
560 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
561
562 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
563
564 let clawback_spend = clawback.recover_spend(&mut ctx, &p2_alice, Conditions::new())?;
565 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
566
567 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
568
569 if !after_expiration {
570 assert!(
571 sim.coin_state(clawback_cat.child(alice.puzzle_hash, 1).coin.coin_id())
572 .is_some()
573 );
574 }
575
576 Ok(())
577 }
578
579 #[rstest]
580 fn test_clawback_v2_force_cat(
581 #[values(false, true)] after_expiration: bool,
582 ) -> anyhow::Result<()> {
583 let mut sim = Simulator::new();
584 let mut ctx = SpendContext::new();
585
586 if after_expiration {
587 sim.set_next_timestamp(100)?;
588 }
589
590 let alice = sim.bls(1);
591 let p2_alice = StandardLayer::new(alice.pk);
592
593 let bob = sim.bls(0);
594
595 let memos = ctx.hint(alice.puzzle_hash)?;
596 let (issue_cat, cats) = Cat::single_issuance(
597 &mut ctx,
598 alice.coin.coin_id(),
599 None,
600 1,
601 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
602 )?;
603 let cat = cats[0];
604 p2_alice.spend(&mut ctx, alice.coin, issue_cat)?;
605
606 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
607 let clawback_puzzle_hash = clawback.tree_hash().into();
608 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
609
610 let inner_spend = p2_alice.spend_with_conditions(
611 &mut ctx,
612 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
613 )?;
614 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
615
616 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
617
618 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
619
620 let clawback_spend = clawback.force_spend(&mut ctx, &p2_alice, Conditions::new())?;
621 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
622
623 expect_spend(sim.spend_coins(ctx.take(), &[alice.sk]), !after_expiration);
624
625 if !after_expiration {
626 assert!(
627 sim.coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
628 .is_some()
629 );
630 }
631
632 Ok(())
633 }
634
635 #[rstest]
636 fn test_clawback_v2_finish_cat(
637 #[values(false, true)] after_expiration: bool,
638 ) -> anyhow::Result<()> {
639 let mut sim = Simulator::new();
640 let mut ctx = SpendContext::new();
641
642 if after_expiration {
643 sim.set_next_timestamp(100)?;
644 }
645
646 let alice = sim.bls(1);
647 let p2_alice = StandardLayer::new(alice.pk);
648
649 let bob = sim.bls(0);
650 let p2_bob = StandardLayer::new(bob.pk);
651
652 let memos = ctx.hint(alice.puzzle_hash)?;
653 let (issue_cat, cats) = Cat::single_issuance(
654 &mut ctx,
655 alice.coin.coin_id(),
656 None,
657 1,
658 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
659 )?;
660 let cat = cats[0];
661 p2_alice.spend(
662 &mut ctx,
663 alice.coin,
664 issue_cat.create_coin(alice.puzzle_hash, 0, Memos::None),
665 )?;
666
667 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
668 let clawback_puzzle_hash = clawback.tree_hash().into();
669 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
670
671 let inner_spend = p2_alice.spend_with_conditions(
672 &mut ctx,
673 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
674 )?;
675 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
676
677 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
678
679 sim.spend_coins(ctx.take(), &[alice.sk])?;
680
681 let clawback_spend = clawback.finish_spend(&mut ctx, &p2_bob, Conditions::new())?;
682 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
683
684 expect_spend(sim.spend_coins(ctx.take(), &[bob.sk]), after_expiration);
685
686 if after_expiration {
687 assert!(
688 sim.coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
689 .is_some()
690 );
691 }
692
693 Ok(())
694 }
695
696 #[rstest]
697 fn test_clawback_v2_push_through_cat(
698 #[values(false, true)] after_expiration: bool,
699 ) -> anyhow::Result<()> {
700 let mut sim = Simulator::new();
701 let mut ctx = SpendContext::new();
702
703 if after_expiration {
704 sim.set_next_timestamp(100)?;
705 }
706
707 let alice = sim.bls(1);
708 let p2_alice = StandardLayer::new(alice.pk);
709
710 let bob = sim.bls(0);
711
712 let memos = ctx.hint(alice.puzzle_hash)?;
713 let (issue_cat, cats) = Cat::single_issuance(
714 &mut ctx,
715 alice.coin.coin_id(),
716 None,
717 1,
718 Conditions::new().create_coin(alice.puzzle_hash, 1, memos),
719 )?;
720 let cat = cats[0];
721 p2_alice.spend(
722 &mut ctx,
723 alice.coin,
724 issue_cat.create_coin(alice.puzzle_hash, 0, Memos::None),
725 )?;
726
727 let clawback = ClawbackV2::new(alice.puzzle_hash, bob.puzzle_hash, 100, 1, true);
728 let clawback_puzzle_hash = clawback.tree_hash().into();
729 let memos = ctx.memos(&clvm_list!(bob.puzzle_hash, clawback.memo()))?;
730
731 let inner_spend = p2_alice.spend_with_conditions(
732 &mut ctx,
733 Conditions::new().create_coin(clawback_puzzle_hash, 1, memos),
734 )?;
735 Cat::spend_all(&mut ctx, &[CatSpend::new(cat, inner_spend)])?;
736
737 let clawback_cat = cat.child(clawback_puzzle_hash, 1);
738
739 sim.spend_coins(ctx.take(), &[alice.sk])?;
740
741 let clawback_spend = clawback.push_through_spend(&mut ctx)?;
742 Cat::spend_all(&mut ctx, &[CatSpend::new(clawback_cat, clawback_spend)])?;
743
744 expect_spend(sim.spend_coins(ctx.take(), &[]), after_expiration);
745
746 if after_expiration {
747 assert!(
748 sim.coin_state(clawback_cat.child(bob.puzzle_hash, 1).coin.coin_id())
749 .is_some()
750 );
751 }
752
753 Ok(())
754 }
755}