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