chia_sdk_driver/primitives/action_layer/
catalog_registry.rs

1use chia_bls::Signature;
2use chia_protocol::{Bytes32, Coin, CoinSpend};
3use chia_puzzle_types::{LineageProof, Proof, singleton::SingletonSolution};
4use chia_sdk_types::puzzles::{CatalogSlotValue, SlotInfo};
5use clvm_traits::{clvm_list, match_tuple};
6use clvm_utils::ToTreeHash;
7use clvmr::NodePtr;
8
9use crate::{
10    ActionLayer, ActionLayerSolution, ActionSingleton, CatalogRefundAction, CatalogRegisterAction,
11    DelegatedStateAction, DriverError, Layer, Puzzle, SingletonAction, Spend, SpendContext,
12};
13
14use super::{CatalogRegistryConstants, CatalogRegistryInfo, CatalogRegistryState, Slot};
15
16#[derive(Debug, Clone)]
17pub struct CatalogPendingSpendInfo {
18    pub actions: Vec<Spend>,
19    pub created_slots: Vec<CatalogSlotValue>,
20    pub spent_slots: Vec<CatalogSlotValue>,
21
22    pub latest_state: (NodePtr, CatalogRegistryState),
23
24    pub signature: Signature,
25}
26
27impl CatalogPendingSpendInfo {
28    pub fn new(latest_state: CatalogRegistryState) -> Self {
29        Self {
30            actions: vec![],
31            created_slots: vec![],
32            spent_slots: vec![],
33            latest_state: (NodePtr::NIL, latest_state),
34            signature: Signature::default(),
35        }
36    }
37}
38
39#[derive(Debug, Clone)]
40#[must_use]
41pub struct CatalogRegistry {
42    pub coin: Coin,
43    pub proof: Proof,
44    pub info: CatalogRegistryInfo,
45
46    pub pending_spend: CatalogPendingSpendInfo,
47}
48
49impl CatalogRegistry {
50    pub fn new(coin: Coin, proof: Proof, info: CatalogRegistryInfo) -> Self {
51        Self {
52            coin,
53            proof,
54            info,
55            pending_spend: CatalogPendingSpendInfo::new(info.state),
56        }
57    }
58}
59
60impl CatalogRegistry {
61    #[allow(clippy::type_complexity)]
62    pub fn pending_info_delta_from_spend(
63        ctx: &mut SpendContext,
64        action_spend: Spend,
65        current_state_and_ephemeral: (NodePtr, CatalogRegistryState),
66        constants: CatalogRegistryConstants,
67    ) -> Result<
68        (
69            (NodePtr, CatalogRegistryState),
70            Vec<CatalogSlotValue>, // created slot values
71            Vec<CatalogSlotValue>, // spent slot values
72        ),
73        DriverError,
74    > {
75        let mut created_slots = vec![];
76        let mut spent_slots = vec![];
77
78        let register_action = CatalogRegisterAction::from_constants(&constants);
79        let register_hash = register_action.tree_hash();
80
81        let refund_action = CatalogRefundAction::from_constants(&constants);
82        let refund_hash = refund_action.tree_hash();
83
84        let delegated_state_action =
85            <DelegatedStateAction as SingletonAction<CatalogRegistry>>::from_constants(&constants);
86        let delegated_state_hash = delegated_state_action.tree_hash();
87
88        let actual_solution = ctx.alloc(&clvm_list!(
89            current_state_and_ephemeral,
90            action_spend.solution
91        ))?;
92
93        let output = ctx.run(action_spend.puzzle, actual_solution)?;
94        let (new_state_and_ephemeral, _) =
95            ctx.extract::<match_tuple!((NodePtr, CatalogRegistryState), NodePtr)>(output)?;
96
97        let raw_action_hash = ctx.tree_hash(action_spend.puzzle);
98
99        if raw_action_hash == register_hash {
100            spent_slots.extend(register_action.spent_slot_values(ctx, action_spend.solution)?);
101
102            created_slots.extend(register_action.created_slot_values(ctx, action_spend.solution)?);
103        } else if raw_action_hash == refund_hash {
104            if let (Some(spent_slot), Some(created_slot)) = (
105                refund_action.spent_slot_value(ctx, action_spend.solution)?,
106                refund_action.created_slot_value(ctx, action_spend.solution)?,
107            ) {
108                spent_slots.push(spent_slot);
109                created_slots.push(created_slot);
110            }
111        } else if raw_action_hash != delegated_state_hash {
112            // delegated state action has no effect on slots
113            return Err(DriverError::InvalidMerkleProof);
114        }
115
116        Ok((new_state_and_ephemeral, created_slots, spent_slots))
117    }
118
119    pub fn pending_info_from_spend(
120        ctx: &mut SpendContext,
121        inner_solution: NodePtr,
122        initial_state: CatalogRegistryState,
123        constants: CatalogRegistryConstants,
124    ) -> Result<CatalogPendingSpendInfo, DriverError> {
125        let mut created_slots = vec![];
126        let mut spent_slots = vec![];
127
128        let mut state_incl_ephemeral: (NodePtr, CatalogRegistryState) =
129            (NodePtr::NIL, initial_state);
130
131        let inner_solution =
132            ActionLayer::<CatalogRegistryState, NodePtr>::parse_solution(ctx, inner_solution)?;
133
134        for raw_action in &inner_solution.action_spends {
135            let res = Self::pending_info_delta_from_spend(
136                ctx,
137                *raw_action,
138                state_incl_ephemeral,
139                constants,
140            )?;
141
142            state_incl_ephemeral = res.0;
143            created_slots.extend(res.1);
144            spent_slots.extend(res.2);
145        }
146
147        Ok(CatalogPendingSpendInfo {
148            actions: inner_solution.action_spends,
149            created_slots,
150            spent_slots,
151            latest_state: state_incl_ephemeral,
152            signature: Signature::default(),
153        })
154    }
155
156    pub fn set_pending_signature(&mut self, signature: Signature) {
157        self.pending_spend.signature = signature;
158    }
159
160    pub fn from_spend(
161        ctx: &mut SpendContext,
162        spend: &CoinSpend,
163        constants: CatalogRegistryConstants,
164    ) -> Result<Option<Self>, DriverError> {
165        let coin = spend.coin;
166        let puzzle_ptr = ctx.alloc(&spend.puzzle_reveal)?;
167        let puzzle = Puzzle::parse(ctx, puzzle_ptr);
168        let solution_ptr = ctx.alloc(&spend.solution)?;
169
170        let Some(info) = CatalogRegistryInfo::parse(ctx, puzzle, constants)? else {
171            return Ok(None);
172        };
173
174        let solution = ctx.extract::<SingletonSolution<NodePtr>>(solution_ptr)?;
175        let proof = solution.lineage_proof;
176
177        let pending_spend =
178            Self::pending_info_from_spend(ctx, solution.inner_solution, info.state, constants)?;
179
180        Ok(Some(CatalogRegistry {
181            coin,
182            proof,
183            info,
184            pending_spend,
185        }))
186    }
187
188    pub fn child_lineage_proof(&self) -> LineageProof {
189        LineageProof {
190            parent_parent_coin_info: self.coin.parent_coin_info,
191            parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
192            parent_amount: self.coin.amount,
193        }
194    }
195
196    pub fn from_parent_spend(
197        ctx: &mut SpendContext,
198        parent_spend: &CoinSpend,
199        constants: CatalogRegistryConstants,
200    ) -> Result<Option<Self>, DriverError>
201    where
202        Self: Sized,
203    {
204        let Some(parent_registry) = CatalogRegistry::from_spend(ctx, parent_spend, constants)?
205        else {
206            return Ok(None);
207        };
208
209        let proof = Proof::Lineage(parent_registry.child_lineage_proof());
210
211        let new_info = parent_registry
212            .info
213            .with_state(parent_registry.pending_spend.latest_state.1);
214        let new_coin = Coin::new(
215            parent_registry.coin.coin_id(),
216            new_info.puzzle_hash().into(),
217            1,
218        );
219
220        Ok(Some(CatalogRegistry {
221            coin: new_coin,
222            proof,
223            info: new_info,
224            pending_spend: CatalogPendingSpendInfo::new(new_info.state),
225        }))
226    }
227
228    pub fn child(&self, child_state: CatalogRegistryState) -> Self {
229        let new_info = self.info.with_state(child_state);
230        let new_coin = Coin::new(self.coin.coin_id(), new_info.puzzle_hash().into(), 1);
231
232        CatalogRegistry {
233            coin: new_coin,
234            proof: Proof::Lineage(self.child_lineage_proof()),
235            info: new_info,
236            pending_spend: CatalogPendingSpendInfo::new(new_info.state),
237        }
238    }
239}
240
241impl ActionSingleton for CatalogRegistry {
242    type State = CatalogRegistryState;
243    type Constants = CatalogRegistryConstants;
244}
245
246impl CatalogRegistry {
247    pub fn finish_spend(self, ctx: &mut SpendContext) -> Result<(Self, Signature), DriverError> {
248        let layers = self.info.into_layers();
249
250        let puzzle = layers.construct_puzzle(ctx)?;
251
252        let action_puzzle_hashes = self
253            .pending_spend
254            .actions
255            .iter()
256            .map(|a| ctx.tree_hash(a.puzzle).into())
257            .collect::<Vec<Bytes32>>();
258
259        let child = self.child(self.pending_spend.latest_state.1);
260        let solution = layers.construct_solution(
261            ctx,
262            SingletonSolution {
263                lineage_proof: self.proof,
264                amount: self.coin.amount,
265                inner_solution: ActionLayerSolution {
266                    proofs: layers
267                        .inner_puzzle
268                        .get_proofs(
269                            &CatalogRegistryInfo::action_puzzle_hashes(&self.info.constants),
270                            &action_puzzle_hashes,
271                        )
272                        .ok_or(DriverError::Custom(
273                            "Couldn't build proofs for one or more actions".to_string(),
274                        ))?,
275                    action_spends: self.pending_spend.actions,
276                    finalizer_solution: NodePtr::NIL,
277                },
278            },
279        )?;
280
281        let my_spend = Spend::new(puzzle, solution);
282        ctx.spend(self.coin, my_spend)?;
283
284        Ok((child, self.pending_spend.signature))
285    }
286
287    pub fn new_action<A>(&self) -> A
288    where
289        A: SingletonAction<Self>,
290    {
291        A::from_constants(&self.info.constants)
292    }
293
294    pub fn created_slot_value_to_slot(
295        &self,
296        slot_value: CatalogSlotValue,
297    ) -> Slot<CatalogSlotValue> {
298        Slot::new(
299            LineageProof {
300                parent_parent_coin_info: self.coin.parent_coin_info,
301                parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
302                parent_amount: self.coin.amount,
303            },
304            SlotInfo::from_value(self.info.constants.launcher_id, 0, slot_value),
305        )
306    }
307
308    pub fn actual_neigbors(
309        &self,
310        new_tail_hash: Bytes32,
311        on_chain_left_slot: Slot<CatalogSlotValue>,
312        on_chain_right_slot: Slot<CatalogSlotValue>,
313    ) -> (Slot<CatalogSlotValue>, Slot<CatalogSlotValue>) {
314        let mut left = on_chain_left_slot;
315        let mut right = on_chain_right_slot;
316
317        for slot_value in &self.pending_spend.created_slots {
318            if slot_value.asset_id < new_tail_hash
319                && slot_value.asset_id >= left.info.value.asset_id
320            {
321                left = self.created_slot_value_to_slot(*slot_value);
322            }
323
324            if slot_value.asset_id > new_tail_hash
325                && slot_value.asset_id <= right.info.value.asset_id
326            {
327                right = self.created_slot_value_to_slot(*slot_value);
328            }
329        }
330
331        (left, right)
332    }
333
334    pub fn actual_slot(&self, slot: Slot<CatalogSlotValue>) -> Slot<CatalogSlotValue> {
335        let mut slot = slot;
336        for slot_value in &self.pending_spend.created_slots {
337            if slot.info.value.asset_id == slot_value.asset_id {
338                slot = self.created_slot_value_to_slot(*slot_value);
339            }
340        }
341
342        slot
343    }
344
345    pub fn insert_action_spend(
346        &mut self,
347        ctx: &mut SpendContext,
348        action_spend: Spend,
349    ) -> Result<(), DriverError> {
350        let res = Self::pending_info_delta_from_spend(
351            ctx,
352            action_spend,
353            self.pending_spend.latest_state,
354            self.info.constants,
355        )?;
356
357        self.pending_spend.latest_state = res.0;
358        self.pending_spend.created_slots.extend(res.1);
359        self.pending_spend.spent_slots.extend(res.2);
360        self.pending_spend.actions.push(action_spend);
361
362        Ok(())
363    }
364}