chia_sdk_driver/actions/
melt_singleton.rs

1use crate::{Deltas, DriverError, Id, SingletonDestination, SpendAction, SpendContext, Spends};
2
3#[derive(Debug, Clone, Copy)]
4pub struct MeltSingletonAction {
5    pub id: Id,
6    pub amount: u64,
7}
8
9impl MeltSingletonAction {
10    pub fn new(id: Id, amount: u64) -> Self {
11        Self { id, amount }
12    }
13}
14
15impl SpendAction for MeltSingletonAction {
16    fn calculate_delta(&self, deltas: &mut Deltas, _index: usize) {
17        deltas.update(self.id).output += self.amount;
18        deltas.update(Id::Xch).input += self.amount;
19    }
20
21    fn spend(
22        &self,
23        _ctx: &mut SpendContext,
24        spends: &mut Spends,
25        _index: usize,
26    ) -> Result<(), DriverError> {
27        if let Some(did) = spends.dids.get_mut(&self.id) {
28            let source = did.last_mut()?;
29            source.child_info.destination = Some(SingletonDestination::Melt);
30        } else if let Some(option) = spends.options.get_mut(&self.id) {
31            let source = option.last_mut()?;
32            source.child_info.destination = Some(SingletonDestination::Melt);
33        } else {
34            return Err(DriverError::InvalidAssetId);
35        }
36
37        Ok(())
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use anyhow::Result;
44    use chia_protocol::Bytes32;
45    use chia_puzzle_types::{
46        offer::{NotarizedPayment, Payment},
47        Memos,
48    };
49    use chia_sdk_test::Simulator;
50    use indexmap::indexmap;
51    use rstest::rstest;
52
53    use crate::{Action, Cat, CatSpend, OptionType, OptionUnderlying, Relation};
54
55    use super::*;
56
57    #[test]
58    fn test_action_melt_did() -> Result<()> {
59        let mut sim = Simulator::new();
60        let mut ctx = SpendContext::new();
61
62        let alice = sim.bls(1);
63
64        let mut spends = Spends::new(alice.puzzle_hash);
65        spends.add(alice.coin);
66
67        let deltas = spends.apply(
68            &mut ctx,
69            &[
70                Action::create_empty_did(),
71                Action::melt_singleton(Id::New(0), 1),
72            ],
73        )?;
74
75        spends.finish_with_keys(
76            &mut ctx,
77            &deltas,
78            Relation::None,
79            &indexmap! { alice.puzzle_hash => alice.pk },
80        )?;
81
82        sim.spend_coins(ctx.take(), &[alice.sk])?;
83
84        Ok(())
85    }
86
87    #[rstest]
88    #[case::normal(None)]
89    #[case::revocable(Some(Bytes32::default()))]
90    fn test_action_exercise_option(#[case] hidden_puzzle_hash: Option<Bytes32>) -> Result<()> {
91        let mut sim = Simulator::new();
92        let mut ctx = SpendContext::new();
93
94        let alice = sim.bls(2);
95        let bob = sim.bls(1);
96        let bob_hint = ctx.hint(bob.puzzle_hash)?;
97
98        let mut spends = Spends::new(alice.puzzle_hash);
99        spends.add(alice.coin);
100
101        let deltas = spends.apply(
102            &mut ctx,
103            &[
104                Action::single_issue_cat(hidden_puzzle_hash, 1),
105                Action::mint_option(
106                    alice.puzzle_hash,
107                    10,
108                    Id::New(0),
109                    1,
110                    OptionType::Xch { amount: 1 },
111                    1,
112                ),
113                Action::send(Id::New(1), bob.puzzle_hash, 1, bob_hint),
114            ],
115        )?;
116
117        let outputs = spends.finish_with_keys(
118            &mut ctx,
119            &deltas,
120            Relation::None,
121            &indexmap! { alice.puzzle_hash => alice.pk },
122        )?;
123
124        let underlying_cat = outputs.cats[&Id::New(0)][0];
125        let option = outputs.options[&Id::New(1)];
126
127        sim.spend_coins(ctx.take(), &[alice.sk])?;
128
129        let underlying = OptionUnderlying::new(
130            option.info.launcher_id,
131            alice.puzzle_hash,
132            10,
133            1,
134            OptionType::Xch { amount: 1 },
135        );
136
137        let underlying_spend =
138            underlying.exercise_spend(&mut ctx, option.info.inner_puzzle_hash().into(), 1)?;
139
140        let settlement_cats =
141            Cat::spend_all(&mut ctx, &[CatSpend::new(underlying_cat, underlying_spend)])?;
142
143        let mut spends = Spends::new(bob.puzzle_hash);
144        spends.add(bob.coin);
145        spends.add(option);
146        spends.add(settlement_cats[0]);
147
148        let deltas = spends.apply(
149            &mut ctx,
150            &[
151                Action::melt_singleton(Id::Existing(option.info.launcher_id), 1),
152                Action::settle(
153                    Id::Xch,
154                    NotarizedPayment::new(
155                        option.info.launcher_id,
156                        vec![Payment::new(alice.puzzle_hash, 1, Memos::None)],
157                    ),
158                ),
159            ],
160        )?;
161
162        spends.finish_with_keys(
163            &mut ctx,
164            &deltas,
165            Relation::None,
166            &indexmap! { bob.puzzle_hash => bob.pk },
167        )?;
168
169        sim.spend_coins(ctx.take(), &[bob.sk])?;
170
171        Ok(())
172    }
173}