1use chia_protocol::{Bytes32, Coin};
2use chia_puzzle_types::{
3 did::DidSolution,
4 singleton::{SingletonArgs, SingletonSolution},
5 LineageProof, Memos, Proof,
6};
7use chia_sdk_types::{run_puzzle, Condition, Conditions};
8use clvm_traits::{FromClvm, ToClvm};
9use clvm_utils::{tree_hash, ToTreeHash};
10use clvmr::{Allocator, NodePtr};
11
12use crate::{
13 DidLayer, DriverError, Layer, Puzzle, SingletonLayer, Spend, SpendContext, SpendWithConditions,
14};
15
16mod did_info;
17mod did_launcher;
18
19pub use did_info::*;
20
21#[must_use]
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct Did<M> {
33 pub coin: Coin,
35
36 pub proof: Proof,
44
45 pub info: DidInfo<M>,
47}
48
49impl<M> Did<M> {
50 pub fn new(coin: Coin, proof: Proof, info: DidInfo<M>) -> Self {
51 Self { coin, proof, info }
52 }
53
54 pub fn with_metadata<N>(self, metadata: N) -> Did<N> {
55 Did {
56 coin: self.coin,
57 proof: self.proof,
58 info: self.info.with_metadata(metadata),
59 }
60 }
61}
62
63impl<M> Did<M>
64where
65 M: ToTreeHash,
66{
67 pub fn child_lineage_proof(&self) -> LineageProof {
69 LineageProof {
70 parent_parent_coin_info: self.coin.parent_coin_info,
71 parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
72 parent_amount: self.coin.amount,
73 }
74 }
75
76 pub fn child<N>(&self, p2_puzzle_hash: Bytes32, metadata: N, amount: u64) -> Did<N>
78 where
79 M: Clone,
80 N: ToTreeHash,
81 {
82 let mut info = self.info.clone().with_metadata(metadata);
83 info.p2_puzzle_hash = p2_puzzle_hash;
84 self.child_with(info, amount)
85 }
86
87 pub fn child_with<N>(&self, info: DidInfo<N>, amount: u64) -> Did<N>
95 where
96 N: ToTreeHash,
97 {
98 Did::new(
99 Coin::new(
100 self.coin.coin_id(),
101 SingletonArgs::curry_tree_hash(info.launcher_id, info.inner_puzzle_hash()).into(),
102 amount,
103 ),
104 Proof::Lineage(self.child_lineage_proof()),
105 info,
106 )
107 }
108}
109
110impl<M> Did<M>
111where
112 M: ToClvm<Allocator> + FromClvm<Allocator> + ToTreeHash + Clone,
113{
114 pub fn spend(
117 &self,
118 ctx: &mut SpendContext,
119 inner_spend: Spend,
120 ) -> Result<Option<Self>, DriverError> {
121 let layers = self.info.clone().into_layers(inner_spend.puzzle);
122
123 let spend = layers.construct_spend(
124 ctx,
125 SingletonSolution {
126 lineage_proof: self.proof,
127 amount: self.coin.amount,
128 inner_solution: DidSolution::Spend(inner_spend.solution),
129 },
130 )?;
131
132 ctx.spend(self.coin, spend)?;
133
134 let output = ctx.run(inner_spend.puzzle, inner_spend.solution)?;
135 let conditions = Vec::<Condition>::from_clvm(ctx, output)?;
136
137 for condition in conditions {
138 if let Some(create_coin) = condition.into_create_coin() {
139 if create_coin.amount % 2 == 1 {
140 let Memos::Some(memos) = create_coin.memos else {
141 return Ok(None);
142 };
143
144 let Some((hint, _)) = <(Bytes32, NodePtr)>::from_clvm(ctx, memos).ok() else {
145 return Ok(None);
146 };
147
148 let child = self.child(hint, self.info.metadata.clone(), create_coin.amount);
149
150 if child.info.inner_puzzle_hash() == create_coin.puzzle_hash.into() {
151 return Ok(Some(child));
152 }
153 }
154 }
155 }
156
157 Ok(None)
158 }
159
160 pub fn spend_with<I>(
166 &self,
167 ctx: &mut SpendContext,
168 inner: &I,
169 conditions: Conditions,
170 ) -> Result<Option<Self>, DriverError>
171 where
172 I: SpendWithConditions,
173 {
174 let inner_spend = inner.spend_with_conditions(ctx, conditions)?;
175 self.spend(ctx, inner_spend)
176 }
177
178 pub fn transfer<I>(
183 self,
184 ctx: &mut SpendContext,
185 inner: &I,
186 p2_puzzle_hash: Bytes32,
187 extra_conditions: Conditions,
188 ) -> Result<Did<M>, DriverError>
189 where
190 M: ToTreeHash,
191 I: SpendWithConditions,
192 {
193 let mut new_info = self.info.clone();
194 new_info.p2_puzzle_hash = p2_puzzle_hash;
195
196 let memos = ctx.hint(p2_puzzle_hash)?;
197
198 self.spend_with(
199 ctx,
200 inner,
201 extra_conditions.create_coin(
202 new_info.inner_puzzle_hash().into(),
203 self.coin.amount,
204 memos,
205 ),
206 )?;
207
208 let metadata = self.info.metadata.clone();
209
210 Ok(self.child(p2_puzzle_hash, metadata, self.coin.amount))
211 }
212
213 pub fn update_with_metadata<I, N>(
222 self,
223 ctx: &mut SpendContext,
224 inner: &I,
225 metadata: N,
226 extra_conditions: Conditions,
227 ) -> Result<Did<N>, DriverError>
228 where
229 I: SpendWithConditions,
230 M: ToTreeHash,
231 N: ToClvm<Allocator> + ToTreeHash + Clone,
232 {
233 let new_inner_puzzle_hash = self
234 .info
235 .clone()
236 .with_metadata(metadata.clone())
237 .inner_puzzle_hash();
238
239 let memos = ctx.hint(self.info.p2_puzzle_hash)?;
240
241 self.spend_with(
242 ctx,
243 inner,
244 extra_conditions.create_coin(new_inner_puzzle_hash.into(), self.coin.amount, memos),
245 )?;
246
247 Ok(self.child(self.info.p2_puzzle_hash, metadata, self.coin.amount))
248 }
249
250 pub fn update<I>(
259 self,
260 ctx: &mut SpendContext,
261 inner: &I,
262 extra_conditions: Conditions,
263 ) -> Result<Did<M>, DriverError>
264 where
265 M: ToTreeHash,
266 I: SpendWithConditions,
267 {
268 let metadata = self.info.metadata.clone();
269 self.update_with_metadata(ctx, inner, metadata, extra_conditions)
270 }
271}
272
273impl<M> Did<M>
274where
275 M: ToClvm<Allocator> + FromClvm<Allocator> + Clone,
276{
277 pub fn parse_child(
282 allocator: &mut Allocator,
283 parent_coin: Coin,
284 parent_puzzle: Puzzle,
285 parent_solution: NodePtr,
286 coin: Coin,
287 ) -> Result<Option<Self>, DriverError>
288 where
289 Self: Sized,
290 {
291 let Some(singleton_layer) =
292 SingletonLayer::<Puzzle>::parse_puzzle(allocator, parent_puzzle)?
293 else {
294 return Ok(None);
295 };
296
297 let Some(did_layer) =
298 DidLayer::<M, Puzzle>::parse_puzzle(allocator, singleton_layer.inner_puzzle)?
299 else {
300 return Ok(None);
301 };
302
303 if singleton_layer.launcher_id != did_layer.launcher_id {
304 return Err(DriverError::InvalidSingletonStruct);
305 }
306
307 let singleton_solution =
308 SingletonLayer::<Puzzle>::parse_solution(allocator, parent_solution)?;
309
310 let output = run_puzzle(
311 allocator,
312 singleton_layer.inner_puzzle.ptr(),
313 singleton_solution.inner_solution,
314 )?;
315 let conditions = Vec::<Condition>::from_clvm(allocator, output)?;
316
317 let Some(create_coin) = conditions
318 .into_iter()
319 .filter_map(Condition::into_create_coin)
320 .find(|create_coin| create_coin.amount % 2 == 1)
321 else {
322 return Err(DriverError::MissingChild);
323 };
324
325 let Memos::Some(memos) = create_coin.memos else {
326 return Err(DriverError::MissingHint);
327 };
328
329 let (hint, _) = <(Bytes32, NodePtr)>::from_clvm(allocator, memos)?;
330
331 let metadata_ptr = did_layer.metadata.to_clvm(allocator)?;
332 let metadata_hash = tree_hash(allocator, metadata_ptr);
333 let did_layer_hashed = did_layer.clone().with_metadata(metadata_hash);
334
335 let parent_inner_puzzle_hash = did_layer_hashed.tree_hash().into();
336 let layers = SingletonLayer::new(singleton_layer.launcher_id, did_layer);
337
338 let mut info = DidInfo::from_layers(layers);
339 info.p2_puzzle_hash = hint;
340
341 Ok(Some(Self {
342 coin,
343 proof: Proof::Lineage(LineageProof {
344 parent_parent_coin_info: parent_coin.parent_coin_info,
345 parent_inner_puzzle_hash,
346 parent_amount: parent_coin.amount,
347 }),
348 info,
349 }))
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use std::fmt;
356
357 use chia_protocol::Bytes32;
358 use chia_sdk_test::Simulator;
359 use clvm_traits::clvm_list;
360 use rstest::rstest;
361
362 use crate::{HashedPtr, Launcher, StandardLayer};
363
364 use super::*;
365
366 #[test]
367 fn test_create_and_update_simple_did() -> anyhow::Result<()> {
368 let mut sim = Simulator::new();
369 let ctx = &mut SpendContext::new();
370
371 let alice = sim.bls(1);
372 let alice_p2 = StandardLayer::new(alice.pk);
373
374 let launcher = Launcher::new(alice.coin.coin_id(), 1);
375 let (create_did, did) = launcher.create_simple_did(ctx, &alice_p2)?;
376 alice_p2.spend(ctx, alice.coin, create_did)?;
377 sim.spend_coins(ctx.take(), &[alice.sk])?;
378
379 assert_eq!(did.info.recovery_list_hash, None);
380 assert_eq!(did.info.num_verifications_required, 1);
381 assert_eq!(did.info.p2_puzzle_hash, alice.puzzle_hash);
382
383 Ok(())
384 }
385
386 #[rstest]
387 fn test_create_and_update_did(
388 #[values(None, Some(Bytes32::default()))] recovery_list_hash: Option<Bytes32>,
389 #[values(0, 1, 3)] num_verifications_required: u64,
390 #[values((), "Atom".to_string(), clvm_list!("Complex".to_string(), 42), 100)]
391 metadata: impl ToClvm<Allocator>
392 + FromClvm<Allocator>
393 + ToTreeHash
394 + Clone
395 + PartialEq
396 + fmt::Debug,
397 ) -> anyhow::Result<()> {
398 let mut sim = Simulator::new();
399 let ctx = &mut SpendContext::new();
400
401 let alice = sim.bls(1);
402 let alice_p2 = StandardLayer::new(alice.pk);
403
404 let launcher = Launcher::new(alice.coin.coin_id(), 1);
405 let (create_did, did) = launcher.create_did(
406 ctx,
407 recovery_list_hash,
408 num_verifications_required,
409 metadata.clone(),
410 &alice_p2,
411 )?;
412 alice_p2.spend(ctx, alice.coin, create_did)?;
413 sim.spend_coins(ctx.take(), &[alice.sk])?;
414
415 assert_eq!(did.info.recovery_list_hash, recovery_list_hash);
416 assert_eq!(
417 did.info.num_verifications_required,
418 num_verifications_required
419 );
420 assert_eq!(did.info.metadata, metadata);
421 assert_eq!(did.info.p2_puzzle_hash, alice.puzzle_hash);
422
423 Ok(())
424 }
425
426 #[test]
427 fn test_transfer_did() -> anyhow::Result<()> {
428 let mut sim = Simulator::new();
429 let ctx = &mut SpendContext::new();
430
431 let alice = sim.bls(1);
432 let alice_p2 = StandardLayer::new(alice.pk);
433
434 let (create_did, alice_did) =
435 Launcher::new(alice.coin.coin_id(), 1).create_simple_did(ctx, &alice_p2)?;
436 alice_p2.spend(ctx, alice.coin, create_did)?;
437
438 let bob = sim.bls(1);
439 let bob_p2 = StandardLayer::new(bob.pk);
440
441 let bob_did = alice_did.transfer(ctx, &alice_p2, bob.puzzle_hash, Conditions::new())?;
442 let did = bob_did.update(ctx, &bob_p2, Conditions::new())?;
443
444 assert_eq!(did.info.p2_puzzle_hash, bob.puzzle_hash);
445 assert_ne!(bob.puzzle_hash, alice.puzzle_hash);
446
447 sim.spend_coins(ctx.take(), &[alice.sk, bob.sk])?;
448
449 Ok(())
450 }
451
452 #[test]
453 fn test_update_did_metadata() -> anyhow::Result<()> {
454 let mut sim = Simulator::new();
455 let ctx = &mut SpendContext::new();
456
457 let alice = sim.bls(1);
458 let alice_p2 = StandardLayer::new(alice.pk);
459
460 let launcher = Launcher::new(alice.coin.coin_id(), 1);
461 let (create_did, did) = launcher.create_simple_did(ctx, &alice_p2)?;
462 alice_p2.spend(ctx, alice.coin, create_did)?;
463 sim.spend_coins(ctx.take(), &[alice.sk])?;
464
465 let new_metadata = "New Metadata".to_string();
466 let updated_did =
467 did.update_with_metadata(ctx, &alice_p2, new_metadata.clone(), Conditions::default())?;
468
469 assert_eq!(updated_did.info.metadata, new_metadata);
470
471 Ok(())
472 }
473
474 #[test]
475 fn test_nodeptr_metadata() -> anyhow::Result<()> {
476 let mut sim = Simulator::new();
477 let ctx = &mut SpendContext::new();
478
479 let alice = sim.bls(1);
480 let alice_p2 = StandardLayer::new(alice.pk);
481
482 let launcher = Launcher::new(alice.coin.coin_id(), 1);
483 let (create_did, did) = launcher.create_did(ctx, None, 1, HashedPtr::NIL, &alice_p2)?;
484 alice_p2.spend(ctx, alice.coin, create_did)?;
485 sim.spend_coins(ctx.take(), &[alice.sk])?;
486
487 let new_metadata = HashedPtr::from_ptr(ctx, ctx.one());
488 let updated_did =
489 did.update_with_metadata(ctx, &alice_p2, new_metadata, Conditions::default())?;
490
491 assert_eq!(updated_did.info.metadata, new_metadata);
492
493 Ok(())
494 }
495
496 #[test]
497 fn test_parse_did() -> anyhow::Result<()> {
498 let mut sim = Simulator::new();
499 let ctx = &mut SpendContext::new();
500
501 let alice = sim.bls(1);
502 let alice_p2 = StandardLayer::new(alice.pk);
503
504 let (create_did, expected_did) =
505 Launcher::new(alice.coin.coin_id(), 1).create_simple_did(ctx, &alice_p2)?;
506 alice_p2.spend(ctx, alice.coin, create_did)?;
507
508 sim.spend_coins(ctx.take(), &[alice.sk])?;
509
510 let mut allocator = Allocator::new();
511
512 let puzzle_reveal = sim
513 .puzzle_reveal(expected_did.coin.parent_coin_info)
514 .expect("missing puzzle")
515 .to_clvm(&mut allocator)?;
516
517 let solution = sim
518 .solution(expected_did.coin.parent_coin_info)
519 .expect("missing solution")
520 .to_clvm(&mut allocator)?;
521
522 let parent_coin = sim
523 .coin_state(expected_did.coin.parent_coin_info)
524 .expect("missing parent coin state")
525 .coin;
526
527 let puzzle = Puzzle::parse(&allocator, puzzle_reveal);
528
529 let did = Did::<()>::parse_child(
530 &mut allocator,
531 parent_coin,
532 puzzle,
533 solution,
534 expected_did.coin,
535 )?
536 .expect("could not parse did");
537
538 assert_eq!(did, expected_did);
539
540 Ok(())
541 }
542}