chia_sdk_parser/puzzles/
did.rs

1use chia_protocol::{Bytes32, Coin};
2use chia_puzzles::{
3    did::{DidArgs, DidSolution, DID_INNER_PUZZLE_HASH},
4    singleton::{SingletonArgs, SingletonStruct},
5    Proof,
6};
7use chia_sdk_types::{
8    conditions::{puzzle_conditions, Condition, CreateCoin},
9    puzzles::DidInfo,
10};
11use clvm_traits::FromClvm;
12use clvm_utils::{tree_hash, CurriedProgram, ToTreeHash, TreeHash};
13use clvmr::{Allocator, NodePtr};
14
15use crate::{ParseError, Puzzle, SingletonPuzzle};
16
17#[derive(Debug, Clone, Copy)]
18pub struct DidPuzzle {
19    pub p2_puzzle: Puzzle,
20    pub recovery_did_list_hash: Bytes32,
21    pub num_verifications_required: u64,
22    pub metadata: NodePtr,
23}
24
25impl DidPuzzle {
26    pub fn parse(
27        allocator: &Allocator,
28        launcher_id: Bytes32,
29        puzzle: &Puzzle,
30    ) -> Result<Option<Self>, ParseError> {
31        let Some(puzzle) = puzzle.as_curried() else {
32            return Ok(None);
33        };
34
35        if puzzle.mod_hash != DID_INNER_PUZZLE_HASH {
36            return Ok(None);
37        }
38
39        let args = DidArgs::<NodePtr, NodePtr>::from_clvm(allocator, puzzle.args)?;
40
41        if args.singleton_struct != SingletonStruct::new(launcher_id) {
42            return Err(ParseError::InvalidSingletonStruct);
43        }
44
45        Ok(Some(DidPuzzle {
46            p2_puzzle: Puzzle::parse(allocator, args.inner_puzzle),
47            recovery_did_list_hash: args.recovery_did_list_hash,
48            num_verifications_required: args.num_verifications_required,
49            metadata: args.metadata,
50        }))
51    }
52
53    pub fn output(
54        &self,
55        allocator: &mut Allocator,
56        solution: NodePtr,
57    ) -> Result<Option<CreateCoin>, ParseError> {
58        let DidSolution::InnerSpend(p2_solution) =
59            DidSolution::<NodePtr>::from_clvm(allocator, solution)?;
60
61        let conditions = puzzle_conditions(allocator, self.p2_puzzle.ptr(), p2_solution)?;
62
63        let create_coin = conditions
64            .into_iter()
65            .find_map(|condition| match condition {
66                Condition::CreateCoin(create_coin) if create_coin.amount % 2 == 1 => {
67                    Some(create_coin)
68                }
69                _ => None,
70            });
71
72        Ok(create_coin)
73    }
74
75    pub fn child_coin_info(
76        &self,
77        allocator: &mut Allocator,
78        singleton: &SingletonPuzzle,
79        parent_coin: Coin,
80        child_coin: Coin,
81        solution: NodePtr,
82    ) -> Result<DidInfo<NodePtr>, ParseError> {
83        let create_coin = self
84            .output(allocator, solution)?
85            .ok_or(ParseError::MissingChild)?;
86
87        let Some(hint) = create_coin.memos.first() else {
88            return Err(ParseError::MissingHint);
89        };
90
91        let p2_puzzle_hash = hint.try_into().map_err(|_| ParseError::MissingHint)?;
92
93        let inner_puzzle_hash = CurriedProgram {
94            program: DID_INNER_PUZZLE_HASH,
95            args: DidArgs {
96                inner_puzzle: TreeHash::from(p2_puzzle_hash),
97                recovery_did_list_hash: self.recovery_did_list_hash,
98                num_verifications_required: self.num_verifications_required,
99                metadata: tree_hash(allocator, self.metadata),
100                singleton_struct: SingletonStruct::new(singleton.launcher_id),
101            },
102        }
103        .tree_hash();
104
105        let singleton_puzzle_hash =
106            SingletonArgs::curry_tree_hash(singleton.launcher_id, inner_puzzle_hash);
107
108        if singleton_puzzle_hash != child_coin.puzzle_hash.into() {
109            return Err(ParseError::MismatchedOutput);
110        }
111
112        Ok(DidInfo {
113            launcher_id: singleton.launcher_id,
114            coin: child_coin,
115            p2_puzzle_hash,
116            inner_puzzle_hash: inner_puzzle_hash.into(),
117            recovery_did_list_hash: self.recovery_did_list_hash,
118            num_verifications_required: self.num_verifications_required,
119            metadata: self.metadata,
120            metadata_hash: tree_hash(allocator, self.metadata),
121            proof: Proof::Lineage(singleton.lineage_proof(parent_coin)),
122        })
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    use chia_bls::PublicKey;
131    use chia_protocol::Coin;
132    use chia_puzzles::{singleton::SingletonSolution, standard::StandardArgs};
133    use chia_sdk_driver::{Launcher, SpendContext};
134    use clvm_traits::ToNodePtr;
135
136    #[test]
137    fn test_parse_did() -> anyhow::Result<()> {
138        let mut ctx = SpendContext::new();
139
140        let pk = PublicKey::default();
141        let puzzle_hash = StandardArgs::curry_tree_hash(pk).into();
142        let parent = Coin::new(Bytes32::default(), puzzle_hash, 1);
143
144        let (create_did, did_info) =
145            Launcher::new(parent.coin_id(), 1).create_simple_did(&mut ctx, pk)?;
146
147        ctx.spend_p2_coin(parent, pk, create_did)?;
148
149        let coin_spends = ctx.take_spends();
150
151        let coin_spend = coin_spends
152            .into_iter()
153            .find(|cs| cs.coin.coin_id() == did_info.coin.parent_coin_info)
154            .unwrap();
155
156        let mut allocator = ctx.into();
157
158        let puzzle_ptr = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?;
159        let solution_ptr = coin_spend.solution.to_node_ptr(&mut allocator)?;
160
161        let puzzle = Puzzle::parse(&allocator, puzzle_ptr);
162
163        let singleton =
164            SingletonPuzzle::parse(&allocator, &puzzle)?.expect("not a singleton puzzle");
165        let singleton_solution = SingletonSolution::<NodePtr>::from_clvm(&allocator, solution_ptr)?;
166
167        let did = DidPuzzle::parse(&allocator, singleton.launcher_id, &singleton.inner_puzzle)?
168            .expect("not a did puzzle");
169
170        let parsed_did_info = did.child_coin_info(
171            &mut allocator,
172            &singleton,
173            coin_spend.coin,
174            did_info.coin,
175            singleton_solution.inner_solution,
176        )?;
177
178        assert_eq!(
179            parsed_did_info,
180            did_info.with_metadata(NodePtr::NIL, ().tree_hash())
181        );
182
183        Ok(())
184    }
185}