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), Vec<XchandlesSlotValue>, Vec<XchandlesSlotValue>, ),
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 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 #[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 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}