chia_sdk_driver/primitives/
did.rs

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
20/// Contains all information needed to spend the outer puzzles of DID coins.
21/// The [`DidInfo`] is used to construct the puzzle, but the [`Proof`] is needed for the solution.
22///
23/// The only thing missing to create a valid coin spend is the inner puzzle and solution.
24/// However, this is handled separately to provide as much flexibility as possible.
25///
26/// This type should contain all of the information you need to store in a database for later.
27/// As long as you can figure out what puzzle the p2 puzzle hash corresponds to and spend it,
28/// you have enough information to spend the DID coin.
29pub type Did = Singleton<DidInfo>;
30
31impl Did {
32    /// Creates a new [`Did`] that represents a child of this one.
33    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    /// Spends this DID coin with the provided inner spend.
45    /// The spend is added to the [`SpendContext`] for convenience.
46    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    /// Spends this DID coin with a [`Layer`] that supports [`SpendWithConditions`].
91    /// This is a building block for built in spend methods, but can also be used to spend
92    /// DID coins with conditions more easily.
93    ///
94    /// However, if you need full flexibility of the inner spend, you can use [`Did::spend`] instead.
95    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    /// Transfers this DID coin to a new p2 puzzle hash.
109    ///
110    /// This spend requires a [`Layer`] that supports [`SpendWithConditions`]. If it doesn't, you can
111    /// use [`Did::spend_with`] instead.
112    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    /// Updates the metadata of this DID.
143    ///
144    /// Because DID coins aren't wrapped automatically, and due to the way they are parsed in wallets,
145    /// an additional update spend is needed. This additional spend is not handled by this method, so
146    /// you will need to do it manually.
147    ///
148    /// This spend requires a [`Layer`] that supports [`SpendWithConditions`]. If it doesn't, you can
149    /// use [`Did::spend_with`] instead.
150    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    /// Spends the DID without changing its metadata or p2 puzzle hash.
178    ///
179    /// This can be done to "settle" the DID's updated metadata and make it parseable by wallets.
180    /// It's also useful if you just want to emit conditions from the DID, without transferring it.
181    /// For example, when assigning a DID to one or more NFTs you can use an update spend to do so.
182    ///
183    /// This spend requires a [`Layer`] that supports [`SpendWithConditions`]. If it doesn't, you can
184    /// use [`Did::spend_with`] instead.
185    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    /// Parses the child of an [`Did`] from the parent coin spend.
198    ///
199    /// This relies on the child being hinted and having the same metadata as the parent.
200    /// If this is not the case, the DID cannot be parsed or spent without additional context.
201    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    /// Parses a [`Did`] and its p2 spend from a coin spend.
273    ///
274    /// If the puzzle is not a DID, this will return [`None`] instead of an error.
275    /// However, if the puzzle should have been a DID but had a parsing error, this will return an error.
276    #[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}