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