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    puzzles::{
8        DelegationLayerArgs, WriterLayerArgs, DELEGATION_LAYER_PUZZLE_HASH,
9        DL_METADATA_UPDATER_PUZZLE_HASH,
10    },
11    MerkleTree,
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 oracle_fee: u64 = BigInt::from_signed_bytes_be(&remaining_memos.remove(0))
80                    .to_u64_digits()
81                    .1[0];
82
83                Ok(DelegatedPuzzle::Oracle(puzzle_hash.into(), oracle_fee))
84            }
85            None => Err(DriverError::MissingMemo),
86        }
87    }
88}
89
90pub trait MetadataWithRootHash {
91    fn root_hash(&self) -> Bytes32;
92    fn root_hash_only(root_hash: Bytes32) -> Self;
93}
94
95impl MetadataWithRootHash for DataStoreMetadata {
96    fn root_hash(&self) -> Bytes32 {
97        self.root_hash
98    }
99
100    fn root_hash_only(root_hash: Bytes32) -> Self {
101        Self {
102            root_hash,
103            label: None,
104            description: None,
105            bytes: None,
106        }
107    }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Default)]
111pub struct DataStoreMetadata {
112    pub root_hash: Bytes32,
113    pub label: Option<String>,
114    pub description: Option<String>,
115    pub bytes: Option<u64>,
116}
117
118impl<N, D: ClvmDecoder<Node = N>> FromClvm<D> for DataStoreMetadata {
119    fn from_clvm(decoder: &D, node: N) -> Result<Self, FromClvmError> {
120        let (root_hash, items) = <(Bytes32, Vec<(String, Raw<N>)>)>::from_clvm(decoder, node)?;
121        let mut metadata = Self::root_hash_only(root_hash);
122
123        for (key, Raw(ptr)) in items {
124            match key.as_str() {
125                "l" => metadata.label = Some(String::from_clvm(decoder, ptr)?),
126                "d" => metadata.description = Some(String::from_clvm(decoder, ptr)?),
127                "b" => metadata.bytes = Some(u64::from_clvm(decoder, ptr)?),
128                _ => (),
129            }
130        }
131
132        Ok(metadata)
133    }
134}
135
136impl<N, E: ClvmEncoder<Node = N>> ToClvm<E> for DataStoreMetadata {
137    fn to_clvm(&self, encoder: &mut E) -> Result<N, ToClvmError> {
138        let mut items: Vec<(&str, Raw<N>)> = Vec::new();
139
140        if let Some(label) = &self.label {
141            items.push(("l", Raw(label.to_clvm(encoder)?)));
142        }
143
144        if let Some(description) = &self.description {
145            items.push(("d", Raw(description.to_clvm(encoder)?)));
146        }
147
148        if let Some(bytes) = self.bytes {
149            items.push(("b", Raw(bytes.to_clvm(encoder)?)));
150        }
151
152        (self.root_hash, items).to_clvm(encoder)
153    }
154}
155
156#[must_use]
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct DataStoreInfo<M = DataStoreMetadata> {
159    pub launcher_id: Bytes32,
160    pub metadata: M,
161    pub owner_puzzle_hash: Bytes32,
162    pub delegated_puzzles: Vec<DelegatedPuzzle>,
163}
164
165impl<M> DataStoreInfo<M> {
166    pub fn new(
167        launcher_id: Bytes32,
168        metadata: M,
169        owner_puzzle_hash: Bytes32,
170        delegated_puzzles: Vec<DelegatedPuzzle>,
171    ) -> Self {
172        Self {
173            launcher_id,
174            metadata,
175            owner_puzzle_hash,
176            delegated_puzzles,
177        }
178    }
179
180    pub fn from_layers_with_delegation_layer(
181        layers: StandardDataStoreLayers<M, DelegationLayer>,
182        delegated_puzzles: Vec<DelegatedPuzzle>,
183    ) -> Self {
184        Self {
185            launcher_id: layers.launcher_id,
186            metadata: layers.inner_puzzle.metadata,
187            owner_puzzle_hash: layers.inner_puzzle.inner_puzzle.owner_puzzle_hash,
188            delegated_puzzles,
189        }
190    }
191
192    pub fn from_layers_without_delegation_layer<I>(layers: StandardDataStoreLayers<M, I>) -> Self
193    where
194        I: ToTreeHash,
195    {
196        Self {
197            launcher_id: layers.launcher_id,
198            metadata: layers.inner_puzzle.metadata,
199            owner_puzzle_hash: layers.inner_puzzle.inner_puzzle.tree_hash().into(),
200            delegated_puzzles: vec![],
201        }
202    }
203
204    pub fn into_layers_with_delegation_layer(
205        self,
206        ctx: &mut SpendContext,
207    ) -> Result<StandardDataStoreLayers<M, DelegationLayer>, DriverError> {
208        Ok(SingletonLayer::new(
209            self.launcher_id,
210            NftStateLayer::new(
211                self.metadata,
212                DL_METADATA_UPDATER_PUZZLE_HASH.into(),
213                DelegationLayer::new(
214                    self.launcher_id,
215                    self.owner_puzzle_hash,
216                    get_merkle_tree(ctx, self.delegated_puzzles)?.root(),
217                ),
218            ),
219        ))
220    }
221
222    #[must_use]
223    pub fn into_layers_without_delegation_layer<I>(
224        self,
225        innermost_layer: I,
226    ) -> StandardDataStoreLayers<M, I> {
227        SingletonLayer::new(
228            self.launcher_id,
229            NftStateLayer::new(
230                self.metadata,
231                DL_METADATA_UPDATER_PUZZLE_HASH.into(),
232                innermost_layer,
233            ),
234        )
235    }
236
237    pub fn inner_puzzle_hash(&self, ctx: &mut SpendContext) -> Result<TreeHash, DriverError>
238    where
239        M: ToClvm<Allocator>,
240    {
241        let metadata_ptr = ctx.alloc(&self.metadata)?;
242
243        if !self.delegated_puzzles.is_empty() {
244            return Ok(NftStateLayerArgs::curry_tree_hash(
245                ctx.tree_hash(metadata_ptr),
246                CurriedProgram {
247                    program: DELEGATION_LAYER_PUZZLE_HASH,
248                    args: DelegationLayerArgs {
249                        mod_hash: DELEGATION_LAYER_PUZZLE_HASH.into(),
250                        launcher_id: self.launcher_id,
251                        owner_puzzle_hash: self.owner_puzzle_hash,
252                        merkle_root: get_merkle_tree(ctx, self.delegated_puzzles.clone())?.root(),
253                    },
254                }
255                .tree_hash(),
256            ));
257        }
258
259        let inner_ph_hash: TreeHash = self.owner_puzzle_hash.into();
260        Ok(NftStateLayerArgs::curry_tree_hash(
261            ctx.tree_hash(metadata_ptr),
262            inner_ph_hash,
263        ))
264    }
265}
266
267pub fn get_merkle_tree(
268    ctx: &mut SpendContext,
269    delegated_puzzles: Vec<DelegatedPuzzle>,
270) -> Result<MerkleTree, DriverError> {
271    let mut leaves = Vec::<Bytes32>::with_capacity(delegated_puzzles.len());
272
273    for dp in delegated_puzzles {
274        match dp {
275            DelegatedPuzzle::Admin(puzzle_hash) => {
276                leaves.push(puzzle_hash.into());
277            }
278            DelegatedPuzzle::Writer(inner_puzzle_hash) => {
279                leaves.push(WriterLayerArgs::curry_tree_hash(inner_puzzle_hash).into());
280            }
281            DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee) => {
282                let oracle_full_puzzle_ptr = OracleLayer::new(oracle_puzzle_hash, oracle_fee)
283                    .ok_or(DriverError::OddOracleFee)?
284                    .construct_puzzle(ctx)?;
285
286                leaves.push(ctx.tree_hash(oracle_full_puzzle_ptr).into());
287            }
288        }
289    }
290
291    Ok(MerkleTree::new(&leaves))
292}