chia_sdk_driver/actions/
run_tail.rs

1use 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        // TODO: Filter outputs better
108        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}