chia_sdk_driver/actions/
issue_cat.rs

1use chia_protocol::{Bytes32, Coin};
2use chia_puzzle_types::{cat::GenesisByCoinIdTailArgs, Memos};
3use chia_sdk_types::{conditions::CreateCoin, Conditions};
4use clvmr::NodePtr;
5
6use crate::{
7    Asset, Cat, CatInfo, Deltas, DriverError, FungibleSpend, Id, Spend, SpendAction, SpendContext,
8    SpendKind, Spends,
9};
10
11#[derive(Debug, Clone, Copy)]
12pub enum TailIssuance {
13    Single,
14    Multiple(Spend),
15}
16
17#[derive(Debug, Clone, Copy)]
18pub struct IssueCatAction {
19    pub issuance: TailIssuance,
20    pub hidden_puzzle_hash: Option<Bytes32>,
21    pub amount: u64,
22}
23
24impl IssueCatAction {
25    pub fn new(issuance: TailIssuance, hidden_puzzle_hash: Option<Bytes32>, amount: u64) -> Self {
26        Self {
27            issuance,
28            hidden_puzzle_hash,
29            amount,
30        }
31    }
32}
33
34impl SpendAction for IssueCatAction {
35    fn calculate_delta(&self, deltas: &mut Deltas, index: usize) {
36        deltas.update(Id::New(index)).input += self.amount;
37        deltas.update(Id::Xch).output += self.amount;
38        deltas.set_needed(Id::Xch);
39    }
40
41    fn spend(
42        &self,
43        ctx: &mut SpendContext,
44        spends: &mut Spends,
45        index: usize,
46    ) -> Result<(), DriverError> {
47        let asset_id = match self.issuance {
48            TailIssuance::Single => None,
49            TailIssuance::Multiple(spend) => Some(ctx.tree_hash(spend.puzzle).into()),
50        };
51
52        let source_index = spends.xch.cat_issuance_source(ctx, asset_id, self.amount)?;
53        let source = &mut spends.xch.items[source_index];
54
55        let asset_id = asset_id.unwrap_or_else(|| {
56            GenesisByCoinIdTailArgs::curry_tree_hash(source.asset.coin_id()).into()
57        });
58
59        let cat_info = CatInfo::new(
60            asset_id,
61            self.hidden_puzzle_hash,
62            source.asset.p2_puzzle_hash(),
63        );
64
65        let create_coin = CreateCoin::new(cat_info.puzzle_hash().into(), self.amount, Memos::None);
66        let parent_puzzle_hash = source.asset.full_puzzle_hash();
67
68        source.kind.create_coin_with_assertion(
69            ctx,
70            parent_puzzle_hash,
71            &mut spends.xch.payment_assertions,
72            create_coin,
73        );
74
75        let eve_cat = Cat::new(
76            Coin::new(
77                source.asset.coin_id(),
78                cat_info.puzzle_hash().into(),
79                self.amount,
80            ),
81            None,
82            cat_info,
83        );
84
85        let id = if spends.cats.contains_key(&Id::Existing(asset_id)) {
86            Id::Existing(asset_id)
87        } else {
88            Id::New(index)
89        };
90
91        let mut cat_spend = FungibleSpend::new(eve_cat, true);
92
93        let tail_spend = match self.issuance {
94            TailIssuance::Single => {
95                let puzzle = ctx.curry(GenesisByCoinIdTailArgs::new(source.asset.coin_id()))?;
96                Spend::new(puzzle, NodePtr::NIL)
97            }
98            TailIssuance::Multiple(spend) => spend,
99        };
100
101        match &mut cat_spend.kind {
102            SpendKind::Conditions(spend) => {
103                spend.add_conditions(
104                    Conditions::new().run_cat_tail(tail_spend.puzzle, tail_spend.solution),
105                );
106            }
107            SpendKind::Settlement(_) => {
108                return Err(DriverError::CannotEmitConditions);
109            }
110        }
111
112        spends.cats.entry(id).or_default().items.push(cat_spend);
113
114        Ok(())
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use anyhow::Result;
121    use chia_puzzle_types::cat::EverythingWithSignatureTailArgs;
122    use chia_sdk_test::Simulator;
123    use indexmap::indexmap;
124    use rstest::rstest;
125
126    use crate::{Action, Relation};
127
128    use super::*;
129
130    #[rstest]
131    #[case::normal(None)]
132    #[case::revocable(Some(Bytes32::default()))]
133    fn test_action_single_issuance_cat(#[case] hidden_puzzle_hash: Option<Bytes32>) -> Result<()> {
134        let mut sim = Simulator::new();
135        let mut ctx = SpendContext::new();
136
137        let alice = sim.bls(1);
138
139        let mut spends = Spends::new(alice.puzzle_hash);
140        spends.add(alice.coin);
141
142        let deltas = spends.apply(&mut ctx, &[Action::single_issue_cat(hidden_puzzle_hash, 1)])?;
143
144        let outputs = spends.finish_with_keys(
145            &mut ctx,
146            &deltas,
147            Relation::None,
148            &indexmap! { alice.puzzle_hash => alice.pk },
149        )?;
150
151        sim.spend_coins(ctx.take(), &[alice.sk])?;
152
153        let cat = outputs.cats[&Id::New(0)][0];
154        assert_ne!(sim.coin_state(cat.coin.coin_id()), None);
155        assert_eq!(cat.info.p2_puzzle_hash, alice.puzzle_hash);
156        assert_eq!(cat.coin.amount, 1);
157
158        Ok(())
159    }
160
161    #[rstest]
162    #[case::normal(None)]
163    #[case::revocable(Some(Bytes32::default()))]
164    fn test_action_multiple_issuance_cat(
165        #[case] hidden_puzzle_hash: Option<Bytes32>,
166    ) -> Result<()> {
167        let mut sim = Simulator::new();
168        let mut ctx = SpendContext::new();
169
170        let alice = sim.bls(1);
171
172        let tail = ctx.curry(EverythingWithSignatureTailArgs::new(alice.pk))?;
173
174        let mut spends = Spends::new(alice.puzzle_hash);
175        spends.add(alice.coin);
176
177        let deltas = spends.apply(
178            &mut ctx,
179            &[Action::issue_cat(
180                Spend::new(tail, NodePtr::NIL),
181                hidden_puzzle_hash,
182                1,
183            )],
184        )?;
185
186        let outputs = spends.finish_with_keys(
187            &mut ctx,
188            &deltas,
189            Relation::None,
190            &indexmap! { alice.puzzle_hash => alice.pk },
191        )?;
192
193        sim.spend_coins(ctx.take(), &[alice.sk])?;
194
195        let cat = outputs.cats[&Id::New(0)][0];
196        assert_ne!(sim.coin_state(cat.coin.coin_id()), None);
197        assert_eq!(cat.info.p2_puzzle_hash, alice.puzzle_hash);
198        assert_eq!(cat.coin.amount, 1);
199
200        Ok(())
201    }
202}