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>, Vec<CatalogSlotValue>, ),
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 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}