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