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}