chia_sdk_driver/primitives/action_layer/
xchandles_registry.rs

1use chia_bls::Signature;
2use chia_protocol::{Bytes32, Coin, CoinSpend};
3use chia_puzzle_types::singleton::{LauncherSolution, SingletonArgs, SingletonSolution};
4use chia_puzzle_types::{LineageProof, Proof};
5use chia_sdk_types::puzzles::{SlotInfo, XchandlesSlotValue};
6use clvm_traits::{clvm_list, match_tuple};
7use clvm_utils::ToTreeHash;
8use clvmr::NodePtr;
9
10use crate::{
11    ActionLayer, ActionLayerSolution, ActionSingleton, DelegatedStateAction, DriverError, Layer,
12    Puzzle, SingletonAction, Spend, SpendContext, XchandlesExpireAction, XchandlesExtendAction,
13    XchandlesOracleAction, XchandlesRefundAction, XchandlesRegisterAction, XchandlesUpdateAction,
14    eve_singleton_inner_puzzle,
15};
16
17use super::{Slot, XchandlesConstants, XchandlesRegistryInfo, XchandlesRegistryState};
18
19#[derive(Debug, Clone)]
20pub struct XchandlesPendingSpendInfo {
21    pub actions: Vec<Spend>,
22    pub spent_slots: Vec<XchandlesSlotValue>,
23    pub created_slots: Vec<XchandlesSlotValue>,
24
25    pub latest_state: (NodePtr, XchandlesRegistryState),
26
27    pub signature: Signature,
28}
29
30impl XchandlesPendingSpendInfo {
31    pub fn new(latest_state: XchandlesRegistryState) -> Self {
32        Self {
33            actions: vec![],
34            created_slots: vec![],
35            spent_slots: vec![],
36            latest_state: (NodePtr::NIL, latest_state),
37            signature: Signature::default(),
38        }
39    }
40}
41
42#[derive(Debug, Clone)]
43#[must_use]
44pub struct XchandlesRegistry {
45    pub coin: Coin,
46    pub proof: Proof,
47    pub info: XchandlesRegistryInfo,
48
49    pub pending_spend: XchandlesPendingSpendInfo,
50}
51
52impl XchandlesRegistry {
53    pub fn new(coin: Coin, proof: Proof, info: XchandlesRegistryInfo) -> Self {
54        Self {
55            coin,
56            proof,
57            info,
58            pending_spend: XchandlesPendingSpendInfo::new(info.state),
59        }
60    }
61}
62
63impl ActionSingleton for XchandlesRegistry {
64    type State = XchandlesRegistryState;
65    type Constants = XchandlesConstants;
66}
67
68impl XchandlesRegistry {
69    #[allow(clippy::type_complexity)]
70    pub fn pending_info_delta_from_spend(
71        ctx: &mut SpendContext,
72        action_spend: Spend,
73        current_state_and_ephemeral: (NodePtr, XchandlesRegistryState),
74        constants: XchandlesConstants,
75    ) -> Result<
76        (
77            (NodePtr, XchandlesRegistryState), // pending state
78            Vec<XchandlesSlotValue>,           // created slot values
79            Vec<XchandlesSlotValue>,           // spent slot values
80        ),
81        DriverError,
82    > {
83        let mut created_slots = vec![];
84        let mut spent_slots = vec![];
85
86        let expire_action = XchandlesExpireAction::from_constants(&constants);
87        let expire_action_hash = expire_action.tree_hash();
88
89        let extend_action = XchandlesExtendAction::from_constants(&constants);
90        let extend_action_hash = extend_action.tree_hash();
91
92        let oracle_action = XchandlesOracleAction::from_constants(&constants);
93        let oracle_action_hash = oracle_action.tree_hash();
94
95        let register_action = XchandlesRegisterAction::from_constants(&constants);
96        let register_action_hash = register_action.tree_hash();
97
98        let update_action = XchandlesUpdateAction::from_constants(&constants);
99        let update_action_hash = update_action.tree_hash();
100
101        let refund_action = XchandlesRefundAction::from_constants(&constants);
102        let refund_action_hash = refund_action.tree_hash();
103
104        let delegated_state_action =
105            <DelegatedStateAction as SingletonAction<XchandlesRegistry>>::from_constants(
106                &constants,
107            );
108        let delegated_state_action_hash = delegated_state_action.tree_hash();
109
110        let actual_solution = ctx.alloc(&clvm_list!(
111            current_state_and_ephemeral,
112            action_spend.solution
113        ))?;
114
115        let output = ctx.run(action_spend.puzzle, actual_solution)?;
116        let (new_state_and_ephemeral, _) =
117            ctx.extract::<match_tuple!((NodePtr, XchandlesRegistryState), NodePtr)>(output)?;
118
119        let raw_action_hash = ctx.tree_hash(action_spend.puzzle);
120
121        if raw_action_hash == extend_action_hash {
122            spent_slots.push(XchandlesExtendAction::spent_slot_value(
123                ctx,
124                action_spend.solution,
125            )?);
126            created_slots.push(XchandlesExtendAction::created_slot_value(
127                ctx,
128                action_spend.solution,
129            )?);
130        } else if raw_action_hash == oracle_action_hash {
131            let slot_value = XchandlesOracleAction::spent_slot_value(ctx, action_spend.solution)?;
132
133            spent_slots.push(slot_value.clone());
134            created_slots.push(slot_value);
135        } else if raw_action_hash == update_action_hash {
136            spent_slots.push(XchandlesUpdateAction::spent_slot_value(
137                ctx,
138                action_spend.solution,
139            )?);
140            created_slots.push(XchandlesUpdateAction::created_slot_value(
141                ctx,
142                action_spend.solution,
143            )?);
144        } else if raw_action_hash == refund_action_hash {
145            if let Some(slot_value) =
146                XchandlesRefundAction::spent_slot_value(ctx, action_spend.solution)?
147            {
148                spent_slots.push(slot_value.clone());
149                created_slots.push(slot_value);
150            }
151        } else if raw_action_hash == expire_action_hash {
152            spent_slots.push(XchandlesExpireAction::spent_slot_value(
153                ctx,
154                action_spend.solution,
155            )?);
156            created_slots.push(XchandlesExpireAction::created_slot_value(
157                ctx,
158                action_spend.solution,
159            )?);
160        } else if raw_action_hash == register_action_hash {
161            spent_slots.extend(XchandlesRegisterAction::spent_slot_values(
162                ctx,
163                action_spend.solution,
164            )?);
165            created_slots.extend(XchandlesRegisterAction::created_slot_values(
166                ctx,
167                action_spend.solution,
168            )?);
169        } else if raw_action_hash != delegated_state_action_hash {
170            // delegated state action has no effect on slots
171            return Err(DriverError::InvalidMerkleProof);
172        }
173
174        Ok((new_state_and_ephemeral, created_slots, spent_slots))
175    }
176
177    pub fn pending_info_from_spend(
178        ctx: &mut SpendContext,
179        inner_solution: NodePtr,
180        initial_state: XchandlesRegistryState,
181        constants: XchandlesConstants,
182    ) -> Result<XchandlesPendingSpendInfo, DriverError> {
183        let mut created_slots = vec![];
184        let mut spent_slots = vec![];
185
186        let mut state_incl_ephemeral: (NodePtr, XchandlesRegistryState) =
187            (NodePtr::NIL, initial_state);
188
189        let inner_solution =
190            ActionLayer::<XchandlesRegistryState, NodePtr>::parse_solution(ctx, inner_solution)?;
191
192        for raw_action in &inner_solution.action_spends {
193            let res = Self::pending_info_delta_from_spend(
194                ctx,
195                *raw_action,
196                state_incl_ephemeral,
197                constants,
198            )?;
199
200            state_incl_ephemeral = res.0;
201            created_slots.extend(res.1);
202            spent_slots.extend(res.2);
203        }
204
205        Ok(XchandlesPendingSpendInfo {
206            actions: inner_solution.action_spends,
207            created_slots,
208            spent_slots,
209            latest_state: state_incl_ephemeral,
210            signature: Signature::default(),
211        })
212    }
213
214    pub fn from_spend(
215        ctx: &mut SpendContext,
216        spend: &CoinSpend,
217        constants: XchandlesConstants,
218    ) -> Result<Option<Self>, DriverError> {
219        let coin = spend.coin;
220        let puzzle_ptr = ctx.alloc(&spend.puzzle_reveal)?;
221        let puzzle = Puzzle::parse(ctx, puzzle_ptr);
222        let solution_ptr = ctx.alloc(&spend.solution)?;
223
224        let Some(info) = XchandlesRegistryInfo::parse(ctx, puzzle, constants)? else {
225            return Ok(None);
226        };
227
228        let solution = ctx.extract::<SingletonSolution<NodePtr>>(solution_ptr)?;
229        let proof = solution.lineage_proof;
230
231        let pending_spend =
232            Self::pending_info_from_spend(ctx, solution.inner_solution, info.state, constants)?;
233
234        Ok(Some(XchandlesRegistry {
235            coin,
236            proof,
237            info,
238            pending_spend,
239        }))
240    }
241
242    pub fn set_pending_signature(&mut self, signature: Signature) {
243        self.pending_spend.signature = signature;
244    }
245
246    pub fn child_lineage_proof(&self) -> LineageProof {
247        LineageProof {
248            parent_parent_coin_info: self.coin.parent_coin_info,
249            parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
250            parent_amount: self.coin.amount,
251        }
252    }
253
254    pub fn from_parent_spend(
255        ctx: &mut SpendContext,
256        parent_spend: &CoinSpend,
257        constants: XchandlesConstants,
258    ) -> Result<Option<Self>, DriverError>
259    where
260        Self: Sized,
261    {
262        let Some(parent_registry) = Self::from_spend(ctx, parent_spend, constants)? else {
263            return Ok(None);
264        };
265
266        let proof = Proof::Lineage(parent_registry.child_lineage_proof());
267
268        let new_info = parent_registry
269            .info
270            .with_state(parent_registry.pending_spend.latest_state.1);
271        let new_coin = Coin::new(
272            parent_registry.coin.coin_id(),
273            new_info.puzzle_hash().into(),
274            1,
275        );
276
277        Ok(Some(XchandlesRegistry {
278            coin: new_coin,
279            proof,
280            info: new_info,
281            pending_spend: XchandlesPendingSpendInfo::new(new_info.state),
282        }))
283    }
284
285    pub fn child(&self, child_state: XchandlesRegistryState) -> Self {
286        let new_info = self.info.with_state(child_state);
287        let new_coin = Coin::new(self.coin.coin_id(), new_info.puzzle_hash().into(), 1);
288
289        XchandlesRegistry {
290            coin: new_coin,
291            proof: Proof::Lineage(self.child_lineage_proof()),
292            info: new_info,
293            pending_spend: XchandlesPendingSpendInfo::new(new_info.state),
294        }
295    }
296
297    // Also returns initial registration asset id
298    #[allow(clippy::type_complexity)]
299    pub fn from_launcher_solution(
300        ctx: &mut SpendContext,
301        launcher_coin: Coin,
302        launcher_solution: NodePtr,
303    ) -> Result<Option<(Self, [Slot<XchandlesSlotValue>; 2], Bytes32, u64)>, DriverError>
304    where
305        Self: Sized,
306    {
307        let Ok(launcher_solution) = ctx.extract::<LauncherSolution<(
308            Bytes32,
309            (
310                u64,
311                (u64, (XchandlesRegistryState, (XchandlesConstants, ()))),
312            ),
313        )>>(launcher_solution) else {
314            return Ok(None);
315        };
316
317        let launcher_id = launcher_coin.coin_id();
318        let (
319            initial_registration_asset_id,
320            (initial_base_price, (initial_registration_period, (initial_state, (constants, ())))),
321        ) = launcher_solution.key_value_list;
322
323        let info = XchandlesRegistryInfo::new(
324            initial_state,
325            constants.with_launcher_id(launcher_coin.coin_id()),
326        );
327        if info.state
328            != XchandlesRegistryState::from(
329                initial_registration_asset_id.tree_hash().into(),
330                initial_base_price,
331                initial_registration_period,
332            )
333        {
334            return Ok(None);
335        }
336
337        let registry_inner_puzzle_hash = info.inner_puzzle_hash().into();
338        let eve_singleton_inner_puzzle = eve_singleton_inner_puzzle(
339            ctx,
340            launcher_id,
341            XchandlesSlotValue::initial_left_end(),
342            XchandlesSlotValue::initial_right_end(),
343            NodePtr::NIL,
344            registry_inner_puzzle_hash,
345        )?;
346        let eve_singleton_inner_puzzle_hash = ctx.tree_hash(eve_singleton_inner_puzzle);
347
348        let eve_coin = Coin::new(
349            launcher_id,
350            SingletonArgs::curry_tree_hash(launcher_id, eve_singleton_inner_puzzle_hash).into(),
351            1,
352        );
353        let registry_coin = Coin::new(
354            eve_coin.coin_id(),
355            SingletonArgs::curry_tree_hash(launcher_id, registry_inner_puzzle_hash.into()).into(),
356            1,
357        );
358
359        if eve_coin.puzzle_hash != launcher_solution.singleton_puzzle_hash {
360            return Ok(None);
361        }
362
363        // proof for registry, which is created by eve singleton
364        let proof = Proof::Lineage(LineageProof {
365            parent_parent_coin_info: eve_coin.parent_coin_info,
366            parent_inner_puzzle_hash: eve_singleton_inner_puzzle_hash.into(),
367            parent_amount: eve_coin.amount,
368        });
369
370        let slot_proof = LineageProof {
371            parent_parent_coin_info: eve_coin.parent_coin_info,
372            parent_inner_puzzle_hash: eve_singleton_inner_puzzle_hash.into(),
373            parent_amount: 1,
374        };
375        let slots = [
376            Slot::new(
377                slot_proof,
378                SlotInfo::from_value(launcher_id, 0, XchandlesSlotValue::initial_left_end()),
379            ),
380            Slot::new(
381                slot_proof,
382                SlotInfo::from_value(launcher_id, 0, XchandlesSlotValue::initial_right_end()),
383            ),
384        ];
385
386        Ok(Some((
387            XchandlesRegistry {
388                coin: registry_coin,
389                proof,
390                info,
391                pending_spend: XchandlesPendingSpendInfo::new(info.state),
392            },
393            slots,
394            initial_registration_asset_id,
395            initial_base_price,
396        )))
397    }
398}
399
400impl XchandlesRegistry {
401    pub fn finish_spend(self, ctx: &mut SpendContext) -> Result<(Self, Signature), DriverError> {
402        let layers = self.info.into_layers();
403
404        let puzzle = layers.construct_puzzle(ctx)?;
405
406        let action_puzzle_hashes = self
407            .pending_spend
408            .actions
409            .iter()
410            .map(|a| ctx.tree_hash(a.puzzle).into())
411            .collect::<Vec<Bytes32>>();
412
413        let child = self.child(self.pending_spend.latest_state.1);
414        let solution = layers.construct_solution(
415            ctx,
416            SingletonSolution {
417                lineage_proof: self.proof,
418                amount: self.coin.amount,
419                inner_solution: ActionLayerSolution {
420                    proofs: layers
421                        .inner_puzzle
422                        .get_proofs(
423                            &XchandlesRegistryInfo::action_puzzle_hashes(&self.info.constants),
424                            &action_puzzle_hashes,
425                        )
426                        .ok_or(DriverError::Custom(
427                            "Couldn't build proofs for one or more actions".to_string(),
428                        ))?,
429                    action_spends: self.pending_spend.actions,
430                    finalizer_solution: NodePtr::NIL,
431                },
432            },
433        )?;
434
435        let my_spend = Spend::new(puzzle, solution);
436        ctx.spend(self.coin, my_spend)?;
437
438        Ok((child, self.pending_spend.signature))
439    }
440
441    pub fn new_action<A>(&self) -> A
442    where
443        A: SingletonAction<Self>,
444    {
445        A::from_constants(&self.info.constants)
446    }
447
448    pub fn created_slot_value_to_slot(
449        &self,
450        slot_value: XchandlesSlotValue,
451    ) -> Slot<XchandlesSlotValue> {
452        let proof = LineageProof {
453            parent_parent_coin_info: self.coin.parent_coin_info,
454            parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
455            parent_amount: 1,
456        };
457
458        Slot::new(
459            proof,
460            SlotInfo::from_value(self.info.constants.launcher_id, 0, slot_value),
461        )
462    }
463
464    pub fn actual_neigbors(
465        &self,
466        new_handle_hash: Bytes32,
467        on_chain_left_slot: Slot<XchandlesSlotValue>,
468        on_chain_right_slot: Slot<XchandlesSlotValue>,
469    ) -> (Slot<XchandlesSlotValue>, Slot<XchandlesSlotValue>) {
470        let mut left = on_chain_left_slot;
471        let mut right = on_chain_right_slot;
472
473        for slot_value in &self.pending_spend.created_slots {
474            if slot_value.handle_hash < new_handle_hash
475                && slot_value.handle_hash >= left.info.value.handle_hash
476            {
477                left = self.created_slot_value_to_slot(slot_value.clone());
478            }
479
480            if slot_value.handle_hash > new_handle_hash
481                && slot_value.handle_hash <= right.info.value.handle_hash
482            {
483                right = self.created_slot_value_to_slot(slot_value.clone());
484            }
485        }
486
487        (left, right)
488    }
489
490    pub fn actual_slot(&self, slot: Slot<XchandlesSlotValue>) -> Slot<XchandlesSlotValue> {
491        let mut slot = slot;
492        for slot_value in &self.pending_spend.created_slots {
493            if slot.info.value.handle_hash == slot_value.handle_hash {
494                slot = self.created_slot_value_to_slot(slot_value.clone());
495            }
496        }
497
498        slot
499    }
500
501    pub fn insert_action_spend(
502        &mut self,
503        ctx: &mut SpendContext,
504        action_spend: Spend,
505    ) -> Result<(), DriverError> {
506        let res = Self::pending_info_delta_from_spend(
507            ctx,
508            action_spend,
509            self.pending_spend.latest_state,
510            self.info.constants,
511        )?;
512
513        self.pending_spend.latest_state = res.0;
514        self.pending_spend.created_slots.extend(res.1);
515        self.pending_spend.spent_slots.extend(res.2);
516        self.pending_spend.actions.push(action_spend);
517
518        Ok(())
519    }
520}