chia_sdk_driver/primitives/datalayer/
datastore_launcher.rs

1use chia_protocol::Bytes32;
2use chia_puzzle_types::{nft::NftStateLayerArgs, EveProof, Proof};
3use chia_puzzles::NFT_STATE_LAYER_HASH;
4use chia_sdk_types::{
5    puzzles::{DelegationLayerArgs, DL_METADATA_UPDATER_PUZZLE_HASH},
6    Conditions,
7};
8use clvm_traits::{FromClvm, ToClvm};
9use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
10use clvmr::Allocator;
11
12use crate::{DriverError, Launcher, SpendContext};
13
14use super::{get_merkle_tree, DataStore, DataStoreInfo, DelegatedPuzzle, DlLauncherKvList};
15
16impl Launcher {
17    pub fn mint_datastore<M>(
18        self,
19        ctx: &mut SpendContext,
20        metadata: M,
21        owner_puzzle_hash: TreeHash,
22        delegated_puzzles: Vec<DelegatedPuzzle>,
23    ) -> Result<(Conditions, DataStore<M>), DriverError>
24    where
25        M: ToClvm<Allocator> + FromClvm<Allocator> + Clone,
26    {
27        let launcher_coin = self.coin();
28        let launcher_id = launcher_coin.coin_id();
29
30        let inner_puzzle_hash: TreeHash = if delegated_puzzles.is_empty() {
31            owner_puzzle_hash
32        } else {
33            DelegationLayerArgs::curry_tree_hash(
34                launcher_id,
35                owner_puzzle_hash.into(),
36                get_merkle_tree(ctx, delegated_puzzles.clone())?.root(),
37            )
38        };
39
40        let metadata_ptr = ctx.alloc(&metadata)?;
41        let metadata_hash = ctx.tree_hash(metadata_ptr);
42        let state_layer_hash = CurriedProgram {
43            program: TreeHash::new(NFT_STATE_LAYER_HASH),
44            args: NftStateLayerArgs::<TreeHash, TreeHash> {
45                mod_hash: NFT_STATE_LAYER_HASH.into(),
46                metadata: metadata_hash,
47                metadata_updater_puzzle_hash: DL_METADATA_UPDATER_PUZZLE_HASH.into(),
48                inner_puzzle: inner_puzzle_hash,
49            },
50        }
51        .tree_hash();
52
53        let mut memos = DataStore::<M>::get_recreation_memos(
54            Bytes32::default(),
55            owner_puzzle_hash,
56            delegated_puzzles.clone(),
57        )
58        .into_iter()
59        .skip(1)
60        .collect();
61        if delegated_puzzles.is_empty() {
62            memos = vec![];
63        }
64        let kv_list = DlLauncherKvList {
65            metadata: metadata.clone(),
66            state_layer_inner_puzzle_hash: inner_puzzle_hash.into(),
67            memos,
68        };
69
70        let (chained_spend, eve_coin) = self.spend(ctx, state_layer_hash.into(), kv_list)?;
71
72        let proof = Proof::Eve(EveProof {
73            parent_parent_coin_info: launcher_coin.parent_coin_info,
74            parent_amount: launcher_coin.amount,
75        });
76
77        let data_store = DataStore {
78            coin: eve_coin,
79            proof,
80            info: DataStoreInfo {
81                launcher_id,
82                metadata,
83                owner_puzzle_hash: owner_puzzle_hash.into(),
84                delegated_puzzles,
85            },
86        };
87
88        Ok((chained_spend, data_store))
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use chia_puzzle_types::standard::StandardArgs;
95    use chia_sdk_test::{BlsPair, Simulator};
96    use rstest::rstest;
97
98    use crate::{
99        tests::{ByteSize, Description, Label, RootHash},
100        DataStoreMetadata, StandardLayer,
101    };
102
103    use super::*;
104
105    #[rstest]
106    fn test_datastore_launch(
107        #[values(true, false)] use_label: bool,
108        #[values(true, false)] use_description: bool,
109        #[values(true, false)] use_byte_size: bool,
110        #[values(true, false)] with_writer: bool,
111        #[values(true, false)] with_admin: bool,
112        #[values(true, false)] with_oracle: bool,
113    ) -> anyhow::Result<()> {
114        let mut sim = Simulator::new();
115
116        let [owner, admin, writer] = BlsPair::range();
117
118        let oracle_puzzle_hash: Bytes32 = [7; 32].into();
119        let oracle_fee = 1000;
120
121        let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
122        let coin = sim.new_coin(owner_puzzle_hash, 1);
123
124        let ctx = &mut SpendContext::new();
125
126        let admin_delegated_puzzle =
127            DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
128        let writer_delegated_puzzle =
129            DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
130        let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
131
132        let mut delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
133        if with_admin {
134            delegated_puzzles.push(admin_delegated_puzzle);
135        }
136        if with_writer {
137            delegated_puzzles.push(writer_delegated_puzzle);
138        }
139        if with_oracle {
140            delegated_puzzles.push(oracle_delegated_puzzle);
141        }
142
143        let metadata = DataStoreMetadata {
144            root_hash: RootHash::Zero.value(),
145            label: if use_label { Label::Some.value() } else { None },
146            description: if use_description {
147                Description::Some.value()
148            } else {
149                None
150            },
151            bytes: if use_byte_size {
152                ByteSize::Some.value()
153            } else {
154                None
155            },
156        };
157
158        let (launch_singleton, datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
159            ctx,
160            metadata.clone(),
161            owner_puzzle_hash.into(),
162            delegated_puzzles,
163        )?;
164        StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
165
166        let spends = ctx.take();
167        for spend in spends.clone() {
168            if spend.coin.coin_id() == datastore.info.launcher_id {
169                let new_datastore = DataStore::from_spend(ctx, &spend, &[])?.unwrap();
170
171                assert_eq!(datastore, new_datastore);
172            }
173
174            ctx.insert(spend);
175        }
176
177        assert_eq!(datastore.info.metadata, metadata);
178
179        sim.spend_coins(spends, &[owner.sk, admin.sk, writer.sk])?;
180
181        // Make sure the datastore was created.
182        let coin_state = sim
183            .coin_state(datastore.coin.coin_id())
184            .expect("expected datastore coin");
185        assert_eq!(coin_state.coin, datastore.coin);
186        assert!(coin_state.created_height.is_some());
187
188        Ok(())
189    }
190}