chia_sdk_driver/primitives/datalayer/
datastore_launcher.rs1use 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 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}