chia_sdk_driver/actions/
run_tail.rs1use chia_sdk_types::Conditions;
2
3use crate::{Delta, Deltas, DriverError, Id, Spend, SpendAction, SpendContext, SpendKind, Spends};
4
5#[derive(Debug, Clone, Copy)]
6pub struct RunTailAction {
7 pub id: Id,
8 pub tail_spend: Spend,
9 pub supply_delta: Delta,
10}
11
12impl RunTailAction {
13 pub fn new(id: Id, tail_spend: Spend, supply_delta: Delta) -> Self {
14 Self {
15 id,
16 tail_spend,
17 supply_delta,
18 }
19 }
20}
21
22impl SpendAction for RunTailAction {
23 fn calculate_delta(&self, deltas: &mut Deltas, _index: usize) {
24 *deltas.update(Id::Xch) += -self.supply_delta;
25 *deltas.update(self.id) += self.supply_delta;
26 deltas.set_needed(self.id);
27 }
28
29 fn spend(
30 &self,
31 ctx: &mut SpendContext,
32 spends: &mut Spends,
33 _index: usize,
34 ) -> Result<(), DriverError> {
35 let cat = spends
36 .cats
37 .get_mut(&self.id)
38 .ok_or(DriverError::InvalidAssetId)?;
39
40 let source_index = cat.run_tail_source(ctx)?;
41 let source = &mut cat.items[source_index];
42
43 match &mut source.kind {
44 SpendKind::Conditions(spend) => {
45 spend.add_conditions(
46 Conditions::new()
47 .run_cat_tail(self.tail_spend.puzzle, self.tail_spend.solution),
48 );
49 }
50 SpendKind::Settlement(_) => {
51 return Err(DriverError::CannotEmitConditions);
52 }
53 }
54
55 Ok(())
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use std::slice;
62
63 use anyhow::Result;
64 use chia_protocol::Bytes32;
65 use chia_puzzle_types::cat::EverythingWithSignatureTailArgs;
66 use chia_sdk_test::Simulator;
67 use clvmr::NodePtr;
68 use indexmap::indexmap;
69 use rstest::rstest;
70
71 use crate::{Action, Relation};
72
73 use super::*;
74
75 #[rstest]
76 #[case::normal(None)]
77 #[case::revocable(Some(Bytes32::default()))]
78 fn test_action_melt_cat(#[case] hidden_puzzle_hash: Option<Bytes32>) -> Result<()> {
79 let mut sim = Simulator::new();
80 let mut ctx = SpendContext::new();
81
82 let alice = sim.bls(1);
83
84 let tail = ctx.curry(EverythingWithSignatureTailArgs::new(alice.pk))?;
85 let tail_spend = Spend::new(tail, NodePtr::NIL);
86
87 let mut spends = Spends::new(alice.puzzle_hash);
88 spends.add(alice.coin);
89
90 let deltas = spends.apply(
91 &mut ctx,
92 &[
93 Action::issue_cat(tail_spend, hidden_puzzle_hash, 1),
94 Action::run_tail(Id::New(0), tail_spend, Delta::new(0, 1)),
95 ],
96 )?;
97
98 let outputs = spends.finish_with_keys(
99 &mut ctx,
100 &deltas,
101 Relation::None,
102 &indexmap! { alice.puzzle_hash => alice.pk },
103 )?;
104
105 sim.spend_coins(ctx.take(), &[alice.sk])?;
106
107 let coin = outputs
109 .xch
110 .iter()
111 .find(|c| c.puzzle_hash == alice.puzzle_hash)
112 .expect("missing coin");
113 assert_ne!(sim.coin_state(coin.coin_id()), None);
114 assert_eq!(coin.amount, 1);
115
116 Ok(())
117 }
118
119 #[rstest]
120 #[case::normal(None)]
121 #[case::revocable(Some(Bytes32::default()))]
122 fn test_action_melt_cat_separate_spends(
123 #[case] hidden_puzzle_hash: Option<Bytes32>,
124 ) -> Result<()> {
125 let mut sim = Simulator::new();
126 let mut ctx = SpendContext::new();
127
128 let alice = sim.bls(1);
129
130 let tail = ctx.curry(EverythingWithSignatureTailArgs::new(alice.pk))?;
131 let tail_spend = Spend::new(tail, NodePtr::NIL);
132
133 let mut spends = Spends::new(alice.puzzle_hash);
134 spends.add(alice.coin);
135
136 let deltas = spends.apply(
137 &mut ctx,
138 &[Action::issue_cat(tail_spend, hidden_puzzle_hash, 1)],
139 )?;
140
141 let outputs = spends.finish_with_keys(
142 &mut ctx,
143 &deltas,
144 Relation::None,
145 &indexmap! { alice.puzzle_hash => alice.pk },
146 )?;
147
148 sim.spend_coins(ctx.take(), slice::from_ref(&alice.sk))?;
149
150 let cat = outputs.cats[&Id::New(0)][0];
151
152 let mut spends = Spends::new(alice.puzzle_hash);
153 spends.add(sim.new_coin(alice.puzzle_hash, 0));
154 spends.add(cat);
155
156 let deltas = spends.apply(
157 &mut ctx,
158 &[Action::run_tail(
159 Id::Existing(cat.info.asset_id),
160 tail_spend,
161 Delta::new(0, 1),
162 )],
163 )?;
164
165 let outputs = spends.finish_with_keys(
166 &mut ctx,
167 &deltas,
168 Relation::None,
169 &indexmap! { alice.puzzle_hash => alice.pk },
170 )?;
171
172 sim.spend_coins(ctx.take(), &[alice.sk])?;
173
174 let coin = outputs.xch[0];
175 assert_ne!(sim.coin_state(coin.coin_id()), None);
176 assert_eq!(coin.puzzle_hash, alice.puzzle_hash);
177 assert_eq!(coin.amount, 1);
178
179 Ok(())
180 }
181}