1use crate::{
2 DelegationLayer, DriverError, Layer, NftStateLayer, OracleLayer, SingletonLayer, SpendContext,
3};
4use chia_protocol::{Bytes, Bytes32};
5use chia_puzzle_types::nft::NftStateLayerArgs;
6use chia_sdk_types::{
7 MerkleTree,
8 puzzles::{
9 DELEGATION_LAYER_PUZZLE_HASH, DL_METADATA_UPDATER_PUZZLE_HASH, DelegationLayerArgs,
10 WriterLayerArgs,
11 },
12};
13use clvm_traits::{ClvmDecoder, ClvmEncoder, FromClvm, FromClvmError, Raw, ToClvm, ToClvmError};
14use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
15use clvmr::Allocator;
16use num_bigint::BigInt;
17
18pub type StandardDatastoreLayers<M = DatastoreMetadata, I = DelegationLayer> =
19 SingletonLayer<NftStateLayer<M, I>>;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)]
22#[repr(u8)]
23#[clvm(atom)]
24pub enum HintType {
25 AdminPuzzle = 1,
27 WriterPuzzle = 2,
28 OraclePuzzle = 3,
29}
30
31impl HintType {
32 pub fn from_value(value: u8) -> Option<Self> {
33 match value {
34 1 => Some(Self::AdminPuzzle),
35 2 => Some(Self::WriterPuzzle),
36 3 => Some(Self::OraclePuzzle),
37 _ => None,
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Copy)]
43pub enum DelegatedPuzzle {
44 Admin(TreeHash), Writer(TreeHash), Oracle(Bytes32, u64), }
48
49impl DelegatedPuzzle {
50 pub fn from_memos(remaining_memos: &mut Vec<Bytes>) -> Result<Self, DriverError> {
51 if remaining_memos.len() < 2 {
52 return Err(DriverError::MissingMemo);
53 }
54
55 let first_memo = remaining_memos.remove(0);
56 if first_memo.len() != 1 {
57 return Err(DriverError::InvalidMemo);
58 }
59 let puzzle_type = HintType::from_value(first_memo[0]);
60
61 let puzzle_hash: TreeHash = TreeHash::new(
63 remaining_memos
64 .remove(0)
65 .to_vec()
66 .try_into()
67 .map_err(|_| DriverError::InvalidMemo)?,
68 );
69
70 match puzzle_type {
71 Some(HintType::AdminPuzzle) => Ok(DelegatedPuzzle::Admin(puzzle_hash)),
72 Some(HintType::WriterPuzzle) => Ok(DelegatedPuzzle::Writer(puzzle_hash)),
73 Some(HintType::OraclePuzzle) => {
74 if remaining_memos.is_empty() {
75 return Err(DriverError::MissingMemo);
76 }
77
78 let fee_memo = remaining_memos.remove(0);
80 let oracle_fee: u64 = if fee_memo.is_empty() {
81 0
82 } else {
83 BigInt::from_signed_bytes_be(&fee_memo)
84 .try_into()
85 .map_err(|_| DriverError::InvalidMemo)?
86 };
87
88 Ok(DelegatedPuzzle::Oracle(puzzle_hash.into(), oracle_fee))
89 }
90 None => Err(DriverError::MissingMemo),
91 }
92 }
93}
94
95pub trait MetadataWithRootHash {
96 fn root_hash(&self) -> Bytes32;
97 fn root_hash_only(root_hash: Bytes32) -> Self;
98}
99
100impl MetadataWithRootHash for DatastoreMetadata {
101 fn root_hash(&self) -> Bytes32 {
102 self.root_hash
103 }
104
105 fn root_hash_only(root_hash: Bytes32) -> Self {
106 Self {
107 root_hash,
108 label: None,
109 description: None,
110 bytes: None,
111 size_proof: None,
112 }
113 }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Default)]
117pub struct DatastoreMetadata {
118 pub root_hash: Bytes32,
119 pub label: Option<String>,
120 pub description: Option<String>,
121 pub bytes: Option<u64>,
122 pub size_proof: Option<String>,
123}
124
125impl<N, D: ClvmDecoder<Node = N>> FromClvm<D> for DatastoreMetadata {
126 fn from_clvm(decoder: &D, node: N) -> Result<Self, FromClvmError> {
127 let (root_hash, items) = <(Bytes32, Vec<(String, Raw<N>)>)>::from_clvm(decoder, node)?;
128 let mut metadata = Self::root_hash_only(root_hash);
129
130 for (key, Raw(ptr)) in items {
131 match key.as_str() {
132 "l" => metadata.label = Some(String::from_clvm(decoder, ptr)?),
133 "d" => metadata.description = Some(String::from_clvm(decoder, ptr)?),
134 "b" => metadata.bytes = Some(u64::from_clvm(decoder, ptr)?),
135 "sp" => metadata.size_proof = Some(String::from_clvm(decoder, ptr)?),
136 _ => (),
137 }
138 }
139
140 Ok(metadata)
141 }
142}
143
144impl<N, E: ClvmEncoder<Node = N>> ToClvm<E> for DatastoreMetadata {
145 fn to_clvm(&self, encoder: &mut E) -> Result<N, ToClvmError> {
146 let mut items: Vec<(&str, Raw<N>)> = Vec::new();
147
148 if let Some(label) = &self.label {
149 items.push(("l", Raw(label.to_clvm(encoder)?)));
150 }
151
152 if let Some(description) = &self.description {
153 items.push(("d", Raw(description.to_clvm(encoder)?)));
154 }
155
156 if let Some(bytes) = self.bytes {
157 items.push(("b", Raw(bytes.to_clvm(encoder)?)));
158 }
159
160 if let Some(size_proof) = &self.size_proof {
161 items.push(("sp", Raw(size_proof.to_clvm(encoder)?)));
162 }
163
164 (self.root_hash, items).to_clvm(encoder)
165 }
166}
167
168#[must_use]
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct DatastoreInfo<M = DatastoreMetadata> {
171 pub launcher_id: Bytes32,
172 pub metadata: M,
173 pub owner_puzzle_hash: Bytes32,
174 pub delegated_puzzles: Vec<DelegatedPuzzle>,
175}
176
177impl<M> DatastoreInfo<M> {
178 pub fn new(
179 launcher_id: Bytes32,
180 metadata: M,
181 owner_puzzle_hash: Bytes32,
182 delegated_puzzles: Vec<DelegatedPuzzle>,
183 ) -> Self {
184 Self {
185 launcher_id,
186 metadata,
187 owner_puzzle_hash,
188 delegated_puzzles,
189 }
190 }
191
192 pub fn from_layers_with_delegation_layer(
193 layers: StandardDatastoreLayers<M, DelegationLayer>,
194 delegated_puzzles: Vec<DelegatedPuzzle>,
195 ) -> Self {
196 Self {
197 launcher_id: layers.launcher_id,
198 metadata: layers.inner_puzzle.metadata,
199 owner_puzzle_hash: layers.inner_puzzle.inner_puzzle.owner_puzzle_hash,
200 delegated_puzzles,
201 }
202 }
203
204 pub fn from_layers_without_delegation_layer<I>(layers: StandardDatastoreLayers<M, I>) -> Self
205 where
206 I: ToTreeHash,
207 {
208 Self {
209 launcher_id: layers.launcher_id,
210 metadata: layers.inner_puzzle.metadata,
211 owner_puzzle_hash: layers.inner_puzzle.inner_puzzle.tree_hash().into(),
212 delegated_puzzles: vec![],
213 }
214 }
215
216 pub fn into_layers_with_delegation_layer(
217 self,
218 ctx: &mut SpendContext,
219 ) -> Result<StandardDatastoreLayers<M, DelegationLayer>, DriverError> {
220 Ok(SingletonLayer::new(
221 self.launcher_id,
222 NftStateLayer::new(
223 self.metadata,
224 DL_METADATA_UPDATER_PUZZLE_HASH.into(),
225 DelegationLayer::new(
226 self.launcher_id,
227 self.owner_puzzle_hash,
228 get_merkle_tree(ctx, self.delegated_puzzles)?.root(),
229 ),
230 ),
231 ))
232 }
233
234 #[must_use]
235 pub fn into_layers_without_delegation_layer<I>(
236 self,
237 innermost_layer: I,
238 ) -> StandardDatastoreLayers<M, I> {
239 SingletonLayer::new(
240 self.launcher_id,
241 NftStateLayer::new(
242 self.metadata,
243 DL_METADATA_UPDATER_PUZZLE_HASH.into(),
244 innermost_layer,
245 ),
246 )
247 }
248
249 pub fn inner_puzzle_hash(&self, ctx: &mut SpendContext) -> Result<TreeHash, DriverError>
250 where
251 M: ToClvm<Allocator>,
252 {
253 let metadata_ptr = ctx.alloc(&self.metadata)?;
254
255 if !self.delegated_puzzles.is_empty() {
256 return Ok(NftStateLayerArgs::curry_tree_hash(
257 ctx.tree_hash(metadata_ptr),
258 CurriedProgram {
259 program: DELEGATION_LAYER_PUZZLE_HASH,
260 args: DelegationLayerArgs {
261 mod_hash: DELEGATION_LAYER_PUZZLE_HASH.into(),
262 launcher_id: self.launcher_id,
263 owner_puzzle_hash: self.owner_puzzle_hash,
264 merkle_root: get_merkle_tree(ctx, self.delegated_puzzles.clone())?.root(),
265 },
266 }
267 .tree_hash(),
268 ));
269 }
270
271 let inner_ph_hash: TreeHash = self.owner_puzzle_hash.into();
272 Ok(NftStateLayerArgs::curry_tree_hash(
273 ctx.tree_hash(metadata_ptr),
274 inner_ph_hash,
275 ))
276 }
277
278 pub fn delegation_layer_puzzle_hash(
279 &self,
280 ctx: &mut SpendContext,
281 ) -> Result<TreeHash, DriverError>
282 where
283 M: ToClvm<Allocator>,
284 {
285 if !self.delegated_puzzles.is_empty() {
286 return Ok(CurriedProgram {
287 program: DELEGATION_LAYER_PUZZLE_HASH,
288 args: DelegationLayerArgs {
289 mod_hash: DELEGATION_LAYER_PUZZLE_HASH.into(),
290 launcher_id: self.launcher_id,
291 owner_puzzle_hash: self.owner_puzzle_hash,
292 merkle_root: get_merkle_tree(ctx, self.delegated_puzzles.clone())?.root(),
293 },
294 }
295 .tree_hash());
296 }
297
298 Ok(self.owner_puzzle_hash.into())
299 }
300}
301
302pub fn get_merkle_tree(
303 ctx: &mut SpendContext,
304 delegated_puzzles: Vec<DelegatedPuzzle>,
305) -> Result<MerkleTree, DriverError> {
306 let mut leaves = Vec::<Bytes32>::with_capacity(delegated_puzzles.len());
307
308 for dp in delegated_puzzles {
309 match dp {
310 DelegatedPuzzle::Admin(puzzle_hash) => {
311 leaves.push(puzzle_hash.into());
312 }
313 DelegatedPuzzle::Writer(inner_puzzle_hash) => {
314 leaves.push(WriterLayerArgs::curry_tree_hash(inner_puzzle_hash).into());
315 }
316 DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee) => {
317 let oracle_full_puzzle_ptr = OracleLayer::new(oracle_puzzle_hash, oracle_fee)
318 .ok_or(DriverError::OddOracleFee)?
319 .construct_puzzle(ctx)?;
320
321 leaves.push(ctx.tree_hash(oracle_full_puzzle_ptr).into());
322 }
323 }
324 }
325
326 Ok(MerkleTree::new(&leaves))
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_oracle_delegated_puzzle_from_memos_zero_fee() -> Result<(), DriverError> {
335 let oracle_puzzle_hash = Bytes32::default();
336 let mut memos = vec![
337 Bytes::new([HintType::OraclePuzzle as u8].into()),
338 oracle_puzzle_hash.into(),
339 Bytes::new(vec![]),
340 ];
341
342 let delegated_puzzle = DelegatedPuzzle::from_memos(&mut memos)?;
343 assert_eq!(
344 delegated_puzzle,
345 DelegatedPuzzle::Oracle(oracle_puzzle_hash, 0)
346 );
347 assert!(memos.is_empty());
348
349 Ok(())
350 }
351}