Skip to main content

chia_sdk_driver/primitives/datalayer/
datastore_info.rs

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    // 0 skipped to prevent confusion with () which is also none (end of list)
26    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),      // puzzle hash
45    Writer(TreeHash),     // inner puzzle hash
46    Oracle(Bytes32, u64), // oracle fee puzzle hash, fee amount
47}
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        // under current specs, first value will always be a puzzle hash
62        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                // puzzle hash bech32m_decode(oracle_address), not puzzle hash of the whole oracle puzze!
79                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}