1use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend};
2use chia_puzzle_types::{
3 nft::{NftStateLayerArgs, NftStateLayerSolution},
4 singleton::{LauncherSolution, SingletonArgs, SingletonSolution},
5 EveProof, LineageProof, Memos, Proof,
6};
7use chia_puzzles::{NFT_STATE_LAYER_HASH, SINGLETON_LAUNCHER_HASH};
8use chia_sdk_types::{
9 conditions::{CreateCoin, NewMetadataInfo, NewMetadataOutput, UpdateNftMetadata},
10 puzzles::{
11 DelegationLayerArgs, DelegationLayerSolution, DELEGATION_LAYER_PUZZLE_HASH,
12 DL_METADATA_UPDATER_PUZZLE_HASH,
13 },
14 run_puzzle, Condition,
15};
16use clvm_traits::{FromClvm, FromClvmError, ToClvm};
17use clvm_utils::{tree_hash, CurriedProgram, ToTreeHash, TreeHash};
18use clvmr::{Allocator, NodePtr};
19use num_bigint::BigInt;
20
21use crate::{DriverError, Layer, NftStateLayer, Puzzle, SingletonLayer, Spend, SpendContext};
22
23use super::{
24 get_merkle_tree, DataStoreInfo, DataStoreMetadata, DelegatedPuzzle, HintType,
25 MetadataWithRootHash,
26};
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct DataStore<M = DataStoreMetadata> {
31 pub coin: Coin,
33 pub proof: Proof,
35 pub info: DataStoreInfo<M>,
37}
38
39impl<M> DataStore<M>
40where
41 M: ToClvm<Allocator> + FromClvm<Allocator>,
42{
43 pub fn new(coin: Coin, proof: Proof, info: DataStoreInfo<M>) -> Self {
44 DataStore { coin, proof, info }
45 }
46
47 pub fn spend(self, ctx: &mut SpendContext, inner_spend: Spend) -> Result<CoinSpend, DriverError>
49 where
50 M: Clone,
51 {
52 let (puzzle_ptr, solution_ptr) = if self.info.delegated_puzzles.is_empty() {
53 let layers = self
54 .info
55 .clone()
56 .into_layers_without_delegation_layer(inner_spend.puzzle);
57
58 let solution_ptr = layers.construct_solution(
59 ctx,
60 SingletonSolution {
61 lineage_proof: self.proof,
62 amount: self.coin.amount,
63 inner_solution: NftStateLayerSolution {
64 inner_solution: inner_spend.solution,
65 },
66 },
67 )?;
68
69 (layers.construct_puzzle(ctx)?, solution_ptr)
70 } else {
71 let layers = self.info.clone().into_layers_with_delegation_layer(ctx)?;
72 let puzzle_ptr = layers.construct_puzzle(ctx)?;
73
74 let delegated_puzzle_hash = ctx.tree_hash(inner_spend.puzzle);
75
76 let tree = get_merkle_tree(ctx, self.info.delegated_puzzles)?;
77
78 let inner_solution = DelegationLayerSolution {
79 merkle_proof: tree.proof(delegated_puzzle_hash.into()),
81 puzzle_reveal: inner_spend.puzzle,
82 puzzle_solution: inner_spend.solution,
83 };
84
85 let solution_ptr = layers.construct_solution(
86 ctx,
87 SingletonSolution {
88 lineage_proof: self.proof,
89 amount: self.coin.amount,
90 inner_solution: NftStateLayerSolution { inner_solution },
91 },
92 )?;
93 (puzzle_ptr, solution_ptr)
94 };
95
96 let puzzle = ctx.serialize(&puzzle_ptr)?;
97 let solution = ctx.serialize(&solution_ptr)?;
98
99 Ok(CoinSpend::new(self.coin, puzzle, solution))
100 }
101
102 pub fn child_lineage_proof(&self, ctx: &mut SpendContext) -> Result<LineageProof, DriverError> {
104 Ok(LineageProof {
105 parent_parent_coin_info: self.coin.parent_coin_info,
106 parent_inner_puzzle_hash: self.info.inner_puzzle_hash(ctx)?.into(),
107 parent_amount: self.coin.amount,
108 })
109 }
110}
111
112#[derive(ToClvm, FromClvm, Debug, Clone, PartialEq, Eq)]
113#[clvm(list)]
114pub struct DlLauncherKvList<M = DataStoreMetadata, T = NodePtr> {
115 pub metadata: M,
116 pub state_layer_inner_puzzle_hash: Bytes32,
117 #[clvm(rest)]
118 pub memos: Vec<T>,
119}
120
121#[derive(ToClvm, FromClvm, Debug, Clone, PartialEq, Eq)]
122#[clvm(list)]
123pub struct OldDlLauncherKvList<T = NodePtr> {
124 pub root_hash: Bytes32,
125 pub state_layer_inner_puzzle_hash: Bytes32,
126 #[clvm(rest)]
127 pub memos: Vec<T>,
128}
129
130impl<M> DataStore<M>
132where
133 M: ToClvm<Allocator> + FromClvm<Allocator> + MetadataWithRootHash,
134{
135 pub fn build_datastore(
136 coin: Coin,
137 launcher_id: Bytes32,
138 proof: Proof,
139 metadata: M,
140 fallback_owner_ph: Bytes32,
141 memos: Vec<Bytes>,
142 ) -> Result<Self, DriverError> {
143 let mut memos = memos;
144
145 if memos.is_empty() {
146 return Ok(DataStore {
148 coin,
149 proof,
150 info: DataStoreInfo {
151 launcher_id,
152 metadata,
153 owner_puzzle_hash: fallback_owner_ph,
154 delegated_puzzles: vec![],
155 },
156 });
157 }
158
159 if memos.drain(0..1).next().ok_or(DriverError::MissingMemo)? != launcher_id.into() {
160 return Err(DriverError::InvalidMemo);
161 }
162
163 if memos.len() == 2 && memos[0] == metadata.root_hash().into() {
164 let owner_puzzle_hash = Bytes32::new(
166 memos[1]
167 .to_vec()
168 .try_into()
169 .map_err(|_| DriverError::InvalidMemo)?,
170 );
171 return Ok(DataStore {
172 coin,
173 proof,
174 info: DataStoreInfo {
175 launcher_id,
176 metadata,
177 owner_puzzle_hash,
178 delegated_puzzles: vec![],
179 },
180 });
181 }
182
183 let owner_puzzle_hash: Bytes32 = if memos.is_empty() {
184 fallback_owner_ph
185 } else {
186 Bytes32::new(
187 memos
188 .drain(0..1)
189 .next()
190 .ok_or(DriverError::MissingMemo)?
191 .to_vec()
192 .try_into()
193 .map_err(|_| DriverError::InvalidMemo)?,
194 )
195 };
196
197 let mut delegated_puzzles = vec![];
198 while memos.len() > 1 {
199 delegated_puzzles.push(DelegatedPuzzle::from_memos(&mut memos)?);
200 }
201
202 Ok(DataStore {
203 coin,
204 proof,
205 info: DataStoreInfo {
206 launcher_id,
207 metadata,
208 owner_puzzle_hash,
209 delegated_puzzles,
210 },
211 })
212 }
213
214 pub fn from_spend(
215 allocator: &mut Allocator,
216 cs: &CoinSpend,
217 parent_delegated_puzzles: &[DelegatedPuzzle],
218 ) -> Result<Option<Self>, DriverError>
219 where
220 Self: Sized,
221 {
222 let solution_node_ptr = cs
223 .solution
224 .to_clvm(allocator)
225 .map_err(DriverError::ToClvm)?;
226
227 if cs.coin.puzzle_hash == SINGLETON_LAUNCHER_HASH.into() {
228 let launcher_id = cs.coin.coin_id();
232
233 let proof = Proof::Eve(EveProof {
234 parent_parent_coin_info: cs.coin.parent_coin_info,
235 parent_amount: cs.coin.amount,
236 });
237
238 let solution = LauncherSolution::<DlLauncherKvList<M, Bytes>>::from_clvm(
239 allocator,
240 solution_node_ptr,
241 );
242
243 return match solution {
244 Ok(solution) => {
245 let metadata = solution.key_value_list.metadata;
246
247 let new_coin = Coin {
248 parent_coin_info: launcher_id,
249 puzzle_hash: solution.singleton_puzzle_hash,
250 amount: solution.amount,
251 };
252
253 let mut memos: Vec<Bytes> = vec![launcher_id.into()];
254 memos.extend(solution.key_value_list.memos);
255
256 Ok(Some(Self::build_datastore(
257 new_coin,
258 launcher_id,
259 proof,
260 metadata,
261 solution.key_value_list.state_layer_inner_puzzle_hash,
262 memos,
263 )?))
264 }
265 Err(err) => match err {
266 FromClvmError::ExpectedPair => {
267 let solution = LauncherSolution::<OldDlLauncherKvList<Bytes>>::from_clvm(
269 allocator,
270 solution_node_ptr,
271 )?;
272
273 let coin = Coin {
274 parent_coin_info: launcher_id,
275 puzzle_hash: solution.singleton_puzzle_hash,
276 amount: solution.amount,
277 };
278
279 Ok(Some(Self::build_datastore(
280 coin,
281 launcher_id,
282 proof,
283 M::root_hash_only(solution.key_value_list.root_hash),
284 solution.key_value_list.state_layer_inner_puzzle_hash,
285 solution.key_value_list.memos,
286 )?))
287 }
288 _ => Err(DriverError::FromClvm(err)),
289 },
290 };
291 }
292
293 let parent_puzzle_ptr = cs
294 .puzzle_reveal
295 .to_clvm(allocator)
296 .map_err(DriverError::ToClvm)?;
297 let parent_puzzle = Puzzle::parse(allocator, parent_puzzle_ptr);
298
299 let Some(singleton_layer) =
300 SingletonLayer::<Puzzle>::parse_puzzle(allocator, parent_puzzle)?
301 else {
302 return Ok(None);
303 };
304
305 let Some(state_layer) =
306 NftStateLayer::<M, Puzzle>::parse_puzzle(allocator, singleton_layer.inner_puzzle)?
307 else {
308 return Ok(None);
309 };
310
311 let parent_solution_ptr = cs.solution.to_clvm(allocator)?;
312 let parent_solution = SingletonLayer::<NftStateLayer<M, Puzzle>>::parse_solution(
313 allocator,
314 parent_solution_ptr,
315 )?;
316
317 let inner_puzzle = state_layer.inner_puzzle.ptr();
319 let inner_solution = parent_solution.inner_solution.inner_solution;
320
321 let inner_output = run_puzzle(allocator, inner_puzzle, inner_solution)?;
322 let inner_conditions = Vec::<Condition>::from_clvm(allocator, inner_output)?;
323
324 let mut inner_create_coin_condition = None;
325 let mut inner_new_metadata_condition = None;
326
327 for condition in inner_conditions {
328 match condition {
329 Condition::CreateCoin(condition) if condition.amount % 2 == 1 => {
330 inner_create_coin_condition = Some(condition);
331 }
332 Condition::UpdateNftMetadata(condition) => {
333 inner_new_metadata_condition = Some(condition);
334 }
335 _ => {}
336 }
337 }
338
339 let Some(inner_create_coin_condition) = inner_create_coin_condition else {
340 return Err(DriverError::MissingChild);
341 };
342
343 let new_metadata = if let Some(inner_new_metadata_condition) = inner_new_metadata_condition
344 {
345 NftStateLayer::<M, NodePtr>::get_next_metadata(
346 allocator,
347 &state_layer.metadata,
348 state_layer.metadata_updater_puzzle_hash,
349 inner_new_metadata_condition,
350 )?
351 } else {
352 state_layer.metadata
353 };
354
355 let new_metadata_ptr = new_metadata.to_clvm(allocator)?;
358 let new_puzzle_hash = SingletonArgs::curry_tree_hash(
359 singleton_layer.launcher_id,
360 CurriedProgram {
361 program: TreeHash::new(NFT_STATE_LAYER_HASH),
362 args: NftStateLayerArgs::<TreeHash, TreeHash> {
363 mod_hash: NFT_STATE_LAYER_HASH.into(),
364 metadata: tree_hash(allocator, new_metadata_ptr),
365 metadata_updater_puzzle_hash: state_layer.metadata_updater_puzzle_hash,
366 inner_puzzle: inner_create_coin_condition.puzzle_hash.into(),
367 },
368 }
369 .tree_hash(),
370 );
371
372 let new_coin = Coin {
373 parent_coin_info: cs.coin.coin_id(),
374 puzzle_hash: new_puzzle_hash.into(),
375 amount: inner_create_coin_condition.amount,
376 };
377
378 let inner_memos = Vec::<Bytes>::from_clvm(
382 allocator,
383 match inner_create_coin_condition.memos {
384 Memos::Some(memos) => memos,
385 Memos::None => NodePtr::NIL,
386 },
387 )?;
388
389 if inner_memos.len() > 1 {
390 return Ok(Some(Self::build_datastore(
392 new_coin,
393 singleton_layer.launcher_id,
394 Proof::Lineage(singleton_layer.lineage_proof(cs.coin)),
395 new_metadata,
396 state_layer.inner_puzzle.tree_hash().into(),
397 inner_memos,
398 )?));
399 }
400
401 let mut owner_puzzle_hash: Bytes32 = state_layer.inner_puzzle.tree_hash().into();
402
403 let delegation_layer_maybe = state_layer.inner_puzzle;
405 if delegation_layer_maybe.is_curried()
406 && delegation_layer_maybe.mod_hash() == DELEGATION_LAYER_PUZZLE_HASH
407 {
408 let deleg_puzzle_args = DelegationLayerArgs::from_clvm(
409 allocator,
410 delegation_layer_maybe
411 .as_curried()
412 .ok_or(DriverError::NonStandardLayer)?
413 .args,
414 )
415 .map_err(DriverError::FromClvm)?;
416 owner_puzzle_hash = deleg_puzzle_args.owner_puzzle_hash;
417
418 let delegation_layer_solution =
419 DelegationLayerSolution::<NodePtr, NodePtr>::from_clvm(allocator, inner_solution)?;
420
421 let output = run_puzzle(
423 allocator,
424 delegation_layer_solution.puzzle_reveal,
425 delegation_layer_solution.puzzle_solution,
426 )?;
427
428 let odd_create_coin = Vec::<NodePtr>::from_clvm(allocator, output)?
429 .iter()
430 .map(|cond| Condition::<NodePtr>::from_clvm(allocator, *cond))
431 .find(|cond| match cond {
432 Ok(Condition::CreateCoin(create_coin)) => create_coin.amount % 2 == 1,
433 _ => false,
434 });
435
436 let Some(odd_create_coin) = odd_create_coin else {
437 return Ok(Some(DataStore {
440 coin: new_coin,
441 proof: Proof::Lineage(singleton_layer.lineage_proof(cs.coin)),
442 info: DataStoreInfo {
443 launcher_id: singleton_layer.launcher_id,
444 metadata: new_metadata,
445 owner_puzzle_hash,
446 delegated_puzzles: parent_delegated_puzzles.to_vec(),
447 },
448 }));
449 };
450
451 let odd_create_coin = odd_create_coin?;
452
453 if let Condition::CreateCoin(create_coin) = odd_create_coin {
457 let prev_deleg_layer_ph = delegation_layer_maybe.tree_hash();
458
459 if create_coin.puzzle_hash == prev_deleg_layer_ph.into() {
460 return Ok(Some(DataStore {
462 coin: new_coin,
463 proof: Proof::Lineage(singleton_layer.lineage_proof(cs.coin)),
464 info: DataStoreInfo {
465 launcher_id: singleton_layer.launcher_id,
466 metadata: new_metadata,
467 owner_puzzle_hash, delegated_puzzles: parent_delegated_puzzles.to_vec(),
469 },
470 }));
471 }
472
473 owner_puzzle_hash = create_coin.puzzle_hash;
475 }
476 }
477
478 Ok(Some(DataStore {
480 coin: new_coin,
481 proof: Proof::Lineage(singleton_layer.lineage_proof(cs.coin)),
482 info: DataStoreInfo {
483 launcher_id: singleton_layer.launcher_id,
484 metadata: new_metadata,
485 owner_puzzle_hash,
486 delegated_puzzles: vec![],
487 },
488 }))
489 }
490}
491
492impl<M> DataStore<M> {
493 pub fn get_recreation_memos(
494 launcher_id: Bytes32,
495 owner_puzzle_hash: TreeHash,
496 delegated_puzzles: Vec<DelegatedPuzzle>,
497 ) -> Vec<Bytes> {
498 let owner_puzzle_hash: Bytes32 = owner_puzzle_hash.into();
499 let mut memos: Vec<Bytes> = vec![launcher_id.into(), owner_puzzle_hash.into()];
500
501 for delegated_puzzle in delegated_puzzles {
502 match delegated_puzzle {
503 DelegatedPuzzle::Admin(inner_puzzle_hash) => {
504 memos.push(Bytes::new([HintType::AdminPuzzle as u8].into()));
505 memos.push(Bytes32::from(inner_puzzle_hash).into());
506 }
507 DelegatedPuzzle::Writer(inner_puzzle_hash) => {
508 memos.push(Bytes::new([HintType::WriterPuzzle as u8].into()));
509 memos.push(Bytes32::from(inner_puzzle_hash).into());
510 }
511 DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee) => {
512 memos.push(Bytes::new([HintType::OraclePuzzle as u8].into()));
513 memos.push(oracle_puzzle_hash.into());
514
515 let fee_bytes = BigInt::from(oracle_fee).to_signed_bytes_be();
516 let mut fee_bytes = fee_bytes.as_slice();
517
518 while (!fee_bytes.is_empty()) && (fee_bytes[0] == 0) {
520 if fee_bytes.len() > 1 && (fee_bytes[1] & 0x80 == 0x80) {
521 break;
522 }
523 fee_bytes = &fee_bytes[1..];
524 }
525
526 memos.push(fee_bytes.into());
527 }
528 }
529 }
530
531 memos
532 }
533
534 pub fn owner_create_coin_condition(
538 ctx: &mut SpendContext,
539 launcher_id: Bytes32,
540 new_inner_puzzle_hash: Bytes32,
541 new_delegated_puzzles: Vec<DelegatedPuzzle>,
542 hint_delegated_puzzles: bool,
543 ) -> Result<Condition, DriverError> {
544 let new_puzzle_hash = if new_delegated_puzzles.is_empty() {
545 new_inner_puzzle_hash
546 } else {
547 let new_merkle_root = get_merkle_tree(ctx, new_delegated_puzzles.clone())?.root();
548 DelegationLayerArgs::curry_tree_hash(
549 launcher_id,
550 new_inner_puzzle_hash,
551 new_merkle_root,
552 )
553 .into()
554 };
555
556 Ok(Condition::CreateCoin(CreateCoin {
557 amount: 1,
558 puzzle_hash: new_puzzle_hash,
559 memos: ctx.memos(&if hint_delegated_puzzles {
560 Self::get_recreation_memos(
561 launcher_id,
562 new_inner_puzzle_hash.into(),
563 new_delegated_puzzles,
564 )
565 } else {
566 vec![launcher_id.into()]
567 })?,
568 }))
569 }
570
571 pub fn new_metadata_condition(
572 ctx: &mut SpendContext,
573 new_metadata: M,
574 ) -> Result<Condition, DriverError>
575 where
576 M: ToClvm<Allocator>,
577 {
578 let new_metadata_condition = UpdateNftMetadata::<i32, NewMetadataOutput<M, ()>> {
579 updater_puzzle_reveal: 11,
580 updater_solution: NewMetadataOutput {
582 metadata_info: NewMetadataInfo::<M> {
583 new_metadata,
584 new_updater_puzzle_hash: DL_METADATA_UPDATER_PUZZLE_HASH.into(),
585 },
586 conditions: (),
587 },
588 }
589 .to_clvm(ctx)?;
590
591 Ok(Condition::Other(new_metadata_condition))
592 }
593}
594
595#[allow(clippy::type_complexity)]
596#[allow(clippy::too_many_arguments)]
597#[cfg(test)]
598pub mod tests {
599 use chia_bls::PublicKey;
600 use chia_puzzle_types::{standard::StandardArgs, Memos};
601 use chia_sdk_test::{BlsPair, Simulator};
602 use chia_sdk_types::{conditions::UpdateDataStoreMerkleRoot, Conditions};
603 use chia_sha2::Sha256;
604 use rstest::rstest;
605
606 use crate::{
607 DelegationLayer, Launcher, OracleLayer, SpendWithConditions, StandardLayer, WriterLayer,
608 };
609
610 use super::*;
611
612 #[derive(Debug, PartialEq, Copy, Clone)]
613 pub enum Label {
614 None,
615 Some,
616 New,
617 }
618
619 impl Label {
620 pub fn value(&self) -> Option<String> {
621 match self {
622 Label::None => None,
623 Label::Some => Some(String::from("label")),
624 Label::New => Some(String::from("new_label")),
625 }
626 }
627 }
628
629 #[derive(Debug, PartialEq, Copy, Clone)]
630 pub enum Description {
631 None,
632 Some,
633 New,
634 }
635
636 impl Description {
637 pub fn value(&self) -> Option<String> {
638 match self {
639 Description::None => None,
640 Description::Some => Some(String::from("description")),
641 Description::New => Some(String::from("new_description")),
642 }
643 }
644 }
645
646 #[derive(Debug, PartialEq, Copy, Clone)]
647 pub enum RootHash {
648 Zero,
649 Some,
650 }
651
652 impl RootHash {
653 pub fn value(&self) -> Bytes32 {
654 match self {
655 RootHash::Zero => Bytes32::from([0; 32]),
656 RootHash::Some => Bytes32::from([1; 32]),
657 }
658 }
659 }
660
661 #[derive(Debug, PartialEq, Copy, Clone)]
662 pub enum ByteSize {
663 None,
664 Some,
665 New,
666 }
667
668 impl ByteSize {
669 pub fn value(&self) -> Option<u64> {
670 match self {
671 ByteSize::None => None,
672 ByteSize::Some => Some(1337),
673 ByteSize::New => Some(42),
674 }
675 }
676 }
677
678 pub fn metadata_from_tuple(t: (RootHash, Label, Description, ByteSize)) -> DataStoreMetadata {
679 DataStoreMetadata {
680 root_hash: t.0.value(),
681 label: t.1.value(),
682 description: t.2.value(),
683 bytes: t.3.value(),
684 size_proof: None, }
686 }
687
688 #[test]
689 fn test_simple_datastore() -> anyhow::Result<()> {
690 let mut sim = Simulator::new();
691
692 let alice = sim.bls(1);
693 let alice_p2 = StandardLayer::new(alice.pk);
694
695 let ctx = &mut SpendContext::new();
696
697 let (launch_singleton, datastore) = Launcher::new(alice.coin.coin_id(), 1).mint_datastore(
698 ctx,
699 DataStoreMetadata::root_hash_only(RootHash::Zero.value()),
700 alice.puzzle_hash.into(),
701 vec![],
702 )?;
703 alice_p2.spend(ctx, alice.coin, launch_singleton)?;
704
705 let spends = ctx.take();
706 for spend in spends {
707 if spend.coin.coin_id() == datastore.info.launcher_id {
708 let new_datastore = DataStore::from_spend(ctx, &spend, &[])?.unwrap();
709
710 assert_eq!(datastore, new_datastore);
711 }
712
713 ctx.insert(spend);
714 }
715
716 let datastore_inner_spend = alice_p2.spend_with_conditions(
717 ctx,
718 Conditions::new().create_coin(alice.puzzle_hash, 1, Memos::None),
719 )?;
720
721 let old_datastore_coin = datastore.coin;
722 let new_spend = datastore.spend(ctx, datastore_inner_spend)?;
723
724 ctx.insert(new_spend);
725
726 sim.spend_coins(ctx.take(), &[alice.sk])?;
727
728 let coin_state = sim
730 .coin_state(old_datastore_coin.coin_id())
731 .expect("expected datastore coin");
732 assert_eq!(coin_state.coin, old_datastore_coin);
733 assert!(coin_state.spent_height.is_some());
734
735 Ok(())
736 }
737
738 #[allow(clippy::similar_names)]
739 #[test]
740 fn test_datastore_with_delegation_layer() -> anyhow::Result<()> {
741 let mut sim = Simulator::new();
742
743 let [owner, admin, writer] = BlsPair::range();
744
745 let oracle_puzzle_hash: Bytes32 = [1; 32].into();
746 let oracle_fee = 1000;
747
748 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
749 let coin = sim.new_coin(owner_puzzle_hash, 1);
750
751 let ctx = &mut SpendContext::new();
752
753 let admin_puzzle = ctx.curry(StandardArgs::new(admin.pk))?;
754 let admin_puzzle_hash = ctx.tree_hash(admin_puzzle);
755
756 let writer_inner_puzzle = ctx.curry(StandardArgs::new(writer.pk))?;
757 let writer_inner_puzzle_hash = ctx.tree_hash(writer_inner_puzzle);
758
759 let admin_delegated_puzzle = DelegatedPuzzle::Admin(admin_puzzle_hash);
760 let writer_delegated_puzzle = DelegatedPuzzle::Writer(writer_inner_puzzle_hash);
761
762 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
763
764 let (launch_singleton, datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
765 ctx,
766 DataStoreMetadata::default(),
767 owner_puzzle_hash.into(),
768 vec![
769 admin_delegated_puzzle,
770 writer_delegated_puzzle,
771 oracle_delegated_puzzle,
772 ],
773 )?;
774 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
775
776 let spends = ctx.take();
777 for spend in spends {
778 if spend.coin.coin_id() == datastore.info.launcher_id {
779 let new_datastore = DataStore::from_spend(ctx, &spend, &[])?.unwrap();
780
781 assert_eq!(datastore, new_datastore);
782 }
783
784 ctx.insert(spend);
785 }
786
787 assert_eq!(datastore.info.metadata.root_hash, RootHash::Zero.value());
788
789 let new_metadata = metadata_from_tuple((
791 RootHash::Some,
792 Label::Some,
793 Description::Some,
794 ByteSize::Some,
795 ));
796
797 let new_metadata_condition = DataStore::new_metadata_condition(ctx, new_metadata.clone())?;
798
799 let inner_spend = WriterLayer::new(StandardLayer::new(writer.pk))
800 .spend(ctx, Conditions::new().with(new_metadata_condition))?;
801 let new_spend = datastore.clone().spend(ctx, inner_spend)?;
802
803 let datastore = DataStore::<DataStoreMetadata>::from_spend(
804 ctx,
805 &new_spend,
806 &datastore.info.delegated_puzzles,
807 )?
808 .unwrap();
809 ctx.insert(new_spend);
810
811 assert_eq!(datastore.info.metadata, new_metadata);
812
813 let delegated_puzzles = vec![admin_delegated_puzzle, oracle_delegated_puzzle];
815 let new_merkle_tree = get_merkle_tree(ctx, delegated_puzzles.clone())?;
816 let new_merkle_root = new_merkle_tree.root();
817
818 let new_merkle_root_condition = ctx.alloc(&UpdateDataStoreMerkleRoot {
819 new_merkle_root,
820 memos: DataStore::<DataStoreMetadata>::get_recreation_memos(
821 datastore.info.launcher_id,
822 owner_puzzle_hash.into(),
823 delegated_puzzles.clone(),
824 ),
825 })?;
826
827 let inner_spend = StandardLayer::new(admin.pk).spend_with_conditions(
828 ctx,
829 Conditions::new().with(Condition::Other(new_merkle_root_condition)),
830 )?;
831 let new_spend = datastore.clone().spend(ctx, inner_spend)?;
832
833 let datastore = DataStore::<DataStoreMetadata>::from_spend(
834 ctx,
835 &new_spend,
836 &datastore.info.delegated_puzzles,
837 )?
838 .unwrap();
839 ctx.insert(new_spend);
840
841 assert!(!datastore.info.delegated_puzzles.is_empty());
842 assert_eq!(datastore.info.delegated_puzzles, delegated_puzzles);
843
844 let oracle_layer = OracleLayer::new(oracle_puzzle_hash, oracle_fee).unwrap();
847 let inner_datastore_spend = oracle_layer.construct_spend(ctx, ())?;
848
849 let new_spend = datastore.clone().spend(ctx, inner_datastore_spend)?;
850
851 let new_datastore = DataStore::<DataStoreMetadata>::from_spend(
852 ctx,
853 &new_spend,
854 &datastore.info.delegated_puzzles,
855 )?
856 .unwrap();
857 ctx.insert(new_spend);
858
859 assert_eq!(new_datastore.info, new_datastore.info);
860 let datastore = new_datastore;
861
862 let new_coin = sim.new_coin(owner_puzzle_hash, oracle_fee);
864
865 let mut hasher = Sha256::new();
866 hasher.update(datastore.coin.puzzle_hash);
867 hasher.update(Bytes::new("$".into()).to_vec());
868
869 StandardLayer::new(owner.pk).spend(
870 ctx,
871 new_coin,
872 Conditions::new().assert_puzzle_announcement(Bytes32::new(hasher.finalize())),
873 )?;
874
875 let owner_layer = StandardLayer::new(owner.pk);
877 let output_condition = DataStore::<DataStoreMetadata>::owner_create_coin_condition(
878 ctx,
879 datastore.info.launcher_id,
880 owner_puzzle_hash,
881 vec![],
882 true,
883 )?;
884 let datastore_remove_delegation_layer_inner_spend =
885 owner_layer.spend_with_conditions(ctx, Conditions::new().with(output_condition))?;
886 let new_spend = datastore
887 .clone()
888 .spend(ctx, datastore_remove_delegation_layer_inner_spend)?;
889
890 let new_datastore =
891 DataStore::<DataStoreMetadata>::from_spend(ctx, &new_spend, &[])?.unwrap();
892 ctx.insert(new_spend);
893
894 assert!(new_datastore.info.delegated_puzzles.is_empty());
895 assert_eq!(new_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
896
897 sim.spend_coins(ctx.take(), &[owner.sk, admin.sk, writer.sk])?;
898
899 let coin_state = sim
901 .coin_state(new_datastore.coin.parent_coin_info)
902 .expect("expected datastore coin");
903 assert_eq!(coin_state.coin, datastore.coin);
904 assert!(coin_state.spent_height.is_some());
905
906 Ok(())
907 }
908
909 #[derive(PartialEq, Debug, Clone, Copy)]
910 pub enum DstAdminLayer {
911 None,
912 Same,
913 New,
914 }
915
916 fn assert_delegated_puzzles_contain(
917 dps: &[DelegatedPuzzle],
918 values: &[DelegatedPuzzle],
919 contained: &[bool],
920 ) {
921 for (i, value) in values.iter().enumerate() {
922 assert_eq!(dps.iter().any(|dp| dp == value), contained[i]);
923 }
924 }
925
926 #[rstest(
927 src_with_writer => [true, false],
928 src_with_oracle => [true, false],
929 dst_with_writer => [true, false],
930 dst_with_oracle => [true, false],
931 src_meta => [
932 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
933 (RootHash::Some, Label::Some, Description::Some, ByteSize::Some),
934 ],
935 dst_meta => [
936 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
937 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
938 (RootHash::Zero, Label::New, Description::New, ByteSize::New),
939 ],
940 dst_admin => [
941 DstAdminLayer::None,
942 DstAdminLayer::Same,
943 DstAdminLayer::New,
944 ]
945 )]
946 #[test]
947 fn test_datastore_admin_transition(
948 src_meta: (RootHash, Label, Description, ByteSize),
949 src_with_writer: bool,
950 src_with_oracle: bool,
952 dst_with_writer: bool,
953 dst_with_oracle: bool,
954 dst_admin: DstAdminLayer,
955 dst_meta: (RootHash, Label, Description, ByteSize),
956 ) -> anyhow::Result<()> {
957 let mut sim = Simulator::new();
958
959 let [owner, admin, admin2, writer] = BlsPair::range();
960
961 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
962 let oracle_fee = 1000;
963
964 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
965 let coin = sim.new_coin(owner_puzzle_hash, 1);
966
967 let ctx = &mut SpendContext::new();
968
969 let admin_delegated_puzzle =
970 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
971 let admin2_delegated_puzzle =
972 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin2.pk));
973 let writer_delegated_puzzle =
974 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
975 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
976
977 let mut src_delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
978 src_delegated_puzzles.push(admin_delegated_puzzle);
979 if src_with_writer {
980 src_delegated_puzzles.push(writer_delegated_puzzle);
981 }
982 if src_with_oracle {
983 src_delegated_puzzles.push(oracle_delegated_puzzle);
984 }
985
986 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
987 ctx,
988 metadata_from_tuple(src_meta),
989 owner_puzzle_hash.into(),
990 src_delegated_puzzles.clone(),
991 )?;
992
993 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
994
995 let mut admin_inner_output = Conditions::new();
997
998 let mut dst_delegated_puzzles: Vec<DelegatedPuzzle> = src_delegated_puzzles.clone();
999 if src_with_writer != dst_with_writer
1000 || src_with_oracle != dst_with_oracle
1001 || dst_admin != DstAdminLayer::Same
1002 {
1003 dst_delegated_puzzles.clear();
1004
1005 if dst_with_writer {
1006 dst_delegated_puzzles.push(writer_delegated_puzzle);
1007 }
1008 if dst_with_oracle {
1009 dst_delegated_puzzles.push(oracle_delegated_puzzle);
1010 }
1011
1012 match dst_admin {
1013 DstAdminLayer::None => {}
1014 DstAdminLayer::Same => {
1015 dst_delegated_puzzles.push(admin_delegated_puzzle);
1016 }
1017 DstAdminLayer::New => {
1018 dst_delegated_puzzles.push(admin2_delegated_puzzle);
1019 }
1020 }
1021
1022 let new_merkle_tree = get_merkle_tree(ctx, dst_delegated_puzzles.clone())?;
1023
1024 let new_merkle_root_condition = ctx.alloc(&UpdateDataStoreMerkleRoot {
1025 new_merkle_root: new_merkle_tree.root(),
1026 memos: DataStore::<DataStoreMetadata>::get_recreation_memos(
1027 src_datastore.info.launcher_id,
1028 owner_puzzle_hash.into(),
1029 dst_delegated_puzzles.clone(),
1030 ),
1031 })?;
1032
1033 admin_inner_output =
1034 admin_inner_output.with(Condition::Other(new_merkle_root_condition));
1035 }
1036
1037 if src_meta != dst_meta {
1038 let new_metadata = metadata_from_tuple(dst_meta);
1039
1040 admin_inner_output =
1041 admin_inner_output.with(DataStore::new_metadata_condition(ctx, new_metadata)?);
1042 }
1043
1044 let inner_datastore_spend =
1046 StandardLayer::new(admin.pk).spend_with_conditions(ctx, admin_inner_output)?;
1047 let src_datastore_coin = src_datastore.coin;
1048 let new_spend = src_datastore.clone().spend(ctx, inner_datastore_spend)?;
1049
1050 let dst_datastore = DataStore::<DataStoreMetadata>::from_spend(
1051 ctx,
1052 &new_spend,
1053 &src_datastore.info.delegated_puzzles,
1054 )?
1055 .unwrap();
1056 ctx.insert(new_spend);
1057
1058 assert_eq!(src_datastore.info.delegated_puzzles, src_delegated_puzzles);
1059 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1060
1061 assert_eq!(src_datastore.info.metadata, metadata_from_tuple(src_meta));
1062
1063 assert_delegated_puzzles_contain(
1064 &src_datastore.info.delegated_puzzles,
1065 &[
1066 admin2_delegated_puzzle,
1067 admin_delegated_puzzle,
1068 writer_delegated_puzzle,
1069 oracle_delegated_puzzle,
1070 ],
1071 &[false, true, src_with_writer, src_with_oracle],
1072 );
1073
1074 assert_eq!(dst_datastore.info.delegated_puzzles, dst_delegated_puzzles);
1075 assert_eq!(dst_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1076
1077 assert_eq!(dst_datastore.info.metadata, metadata_from_tuple(dst_meta));
1078
1079 assert_delegated_puzzles_contain(
1080 &dst_datastore.info.delegated_puzzles,
1081 &[
1082 admin2_delegated_puzzle,
1083 admin_delegated_puzzle,
1084 writer_delegated_puzzle,
1085 oracle_delegated_puzzle,
1086 ],
1087 &[
1088 dst_admin == DstAdminLayer::New,
1089 dst_admin == DstAdminLayer::Same,
1090 dst_with_writer,
1091 dst_with_oracle,
1092 ],
1093 );
1094
1095 sim.spend_coins(ctx.take(), &[owner.sk, admin.sk, writer.sk])?;
1096
1097 let src_coin_state = sim
1098 .coin_state(src_datastore_coin.coin_id())
1099 .expect("expected src datastore coin");
1100 assert_eq!(src_coin_state.coin, src_datastore_coin);
1101 assert!(src_coin_state.spent_height.is_some());
1102 let dst_coin_state = sim
1103 .coin_state(dst_datastore.coin.coin_id())
1104 .expect("expected dst datastore coin");
1105 assert_eq!(dst_coin_state.coin, dst_datastore.coin);
1106 assert!(dst_coin_state.created_height.is_some());
1107
1108 Ok(())
1109 }
1110
1111 #[rstest(
1112 src_with_admin => [true, false],
1113 src_with_writer => [true, false],
1114 src_with_oracle => [true, false],
1115 dst_with_admin => [true, false],
1116 dst_with_writer => [true, false],
1117 dst_with_oracle => [true, false],
1118 src_meta => [
1119 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1120 (RootHash::Some, Label::Some, Description::Some, ByteSize::Some),
1121 ],
1122 dst_meta => [
1123 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1124 (RootHash::Some, Label::Some, Description::Some, ByteSize::Some),
1125 (RootHash::Some, Label::New, Description::New, ByteSize::New),
1126 ],
1127 change_owner => [true, false],
1128 )]
1129 #[test]
1130 fn test_datastore_owner_transition(
1131 src_meta: (RootHash, Label, Description, ByteSize),
1132 src_with_admin: bool,
1133 src_with_writer: bool,
1134 src_with_oracle: bool,
1135 dst_with_admin: bool,
1136 dst_with_writer: bool,
1137 dst_with_oracle: bool,
1138 dst_meta: (RootHash, Label, Description, ByteSize),
1139 change_owner: bool,
1140 ) -> anyhow::Result<()> {
1141 let mut sim = Simulator::new();
1142
1143 let [owner, owner2, admin, writer] = BlsPair::range();
1144
1145 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
1146 let oracle_fee = 1000;
1147
1148 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1149 let coin = sim.new_coin(owner_puzzle_hash, 1);
1150
1151 let owner2_puzzle_hash = StandardArgs::curry_tree_hash(owner2.pk).into();
1152 assert_ne!(owner_puzzle_hash, owner2_puzzle_hash);
1153
1154 let ctx = &mut SpendContext::new();
1155
1156 let admin_delegated_puzzle =
1157 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
1158 let writer_delegated_puzzle =
1159 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
1160 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
1161
1162 let mut src_delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
1163 if src_with_admin {
1164 src_delegated_puzzles.push(admin_delegated_puzzle);
1165 }
1166 if src_with_writer {
1167 src_delegated_puzzles.push(writer_delegated_puzzle);
1168 }
1169 if src_with_oracle {
1170 src_delegated_puzzles.push(oracle_delegated_puzzle);
1171 }
1172
1173 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1174 ctx,
1175 metadata_from_tuple(src_meta),
1176 owner_puzzle_hash.into(),
1177 src_delegated_puzzles.clone(),
1178 )?;
1179 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1180
1181 let mut owner_output_conds = Conditions::new();
1183
1184 let mut dst_delegated_puzzles: Vec<DelegatedPuzzle> = src_delegated_puzzles.clone();
1185 let mut hint_new_delegated_puzzles = change_owner;
1186 if src_with_admin != dst_with_admin
1187 || src_with_writer != dst_with_writer
1188 || src_with_oracle != dst_with_oracle
1189 || dst_delegated_puzzles.is_empty()
1190 {
1191 dst_delegated_puzzles.clear();
1192 hint_new_delegated_puzzles = true;
1193
1194 if dst_with_admin {
1195 dst_delegated_puzzles.push(admin_delegated_puzzle);
1196 }
1197 if dst_with_writer {
1198 dst_delegated_puzzles.push(writer_delegated_puzzle);
1199 }
1200 if dst_with_oracle {
1201 dst_delegated_puzzles.push(oracle_delegated_puzzle);
1202 }
1203 }
1204
1205 owner_output_conds =
1206 owner_output_conds.with(DataStore::<DataStoreMetadata>::owner_create_coin_condition(
1207 ctx,
1208 src_datastore.info.launcher_id,
1209 if change_owner {
1210 owner2_puzzle_hash
1211 } else {
1212 owner_puzzle_hash
1213 },
1214 dst_delegated_puzzles.clone(),
1215 hint_new_delegated_puzzles,
1216 )?);
1217
1218 if src_meta != dst_meta {
1219 let new_metadata = metadata_from_tuple(dst_meta);
1220
1221 owner_output_conds =
1222 owner_output_conds.with(DataStore::new_metadata_condition(ctx, new_metadata)?);
1223 }
1224
1225 let inner_datastore_spend =
1227 StandardLayer::new(owner.pk).spend_with_conditions(ctx, owner_output_conds)?;
1228 let new_spend = src_datastore.clone().spend(ctx, inner_datastore_spend)?;
1229
1230 let dst_datastore = DataStore::<DataStoreMetadata>::from_spend(
1231 ctx,
1232 &new_spend,
1233 &src_datastore.info.delegated_puzzles,
1234 )?
1235 .unwrap();
1236
1237 ctx.insert(new_spend);
1238
1239 assert_eq!(src_datastore.info.delegated_puzzles, src_delegated_puzzles);
1240 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1241
1242 assert_eq!(src_datastore.info.metadata, metadata_from_tuple(src_meta));
1243
1244 assert_delegated_puzzles_contain(
1245 &src_datastore.info.delegated_puzzles,
1246 &[
1247 admin_delegated_puzzle,
1248 writer_delegated_puzzle,
1249 oracle_delegated_puzzle,
1250 ],
1251 &[src_with_admin, src_with_writer, src_with_oracle],
1252 );
1253
1254 assert_eq!(dst_datastore.info.delegated_puzzles, dst_delegated_puzzles);
1255 assert_eq!(
1256 dst_datastore.info.owner_puzzle_hash,
1257 if change_owner {
1258 owner2_puzzle_hash
1259 } else {
1260 owner_puzzle_hash
1261 }
1262 );
1263
1264 assert_eq!(dst_datastore.info.metadata, metadata_from_tuple(dst_meta));
1265
1266 assert_delegated_puzzles_contain(
1267 &dst_datastore.info.delegated_puzzles,
1268 &[
1269 admin_delegated_puzzle,
1270 writer_delegated_puzzle,
1271 oracle_delegated_puzzle,
1272 ],
1273 &[dst_with_admin, dst_with_writer, dst_with_oracle],
1274 );
1275
1276 sim.spend_coins(ctx.take(), &[owner.sk, admin.sk, writer.sk])?;
1277
1278 let src_coin_state = sim
1279 .coin_state(src_datastore.coin.coin_id())
1280 .expect("expected src datastore coin");
1281 assert_eq!(src_coin_state.coin, src_datastore.coin);
1282 assert!(src_coin_state.spent_height.is_some());
1283
1284 let dst_coin_state = sim
1285 .coin_state(dst_datastore.coin.coin_id())
1286 .expect("expected dst datastore coin");
1287 assert_eq!(dst_coin_state.coin, dst_datastore.coin);
1288 assert!(dst_coin_state.created_height.is_some());
1289
1290 Ok(())
1291 }
1292
1293 #[rstest(
1294 with_admin_layer => [true, false],
1295 with_oracle_layer => [true, false],
1296 meta_transition => [
1297 (
1298 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1299 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1300 ),
1301 (
1302 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1303 (RootHash::Some, Label::None, Description::None, ByteSize::None),
1304 ),
1305 (
1306 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1307 (RootHash::Some, Label::Some, Description::Some, ByteSize::Some),
1308 ),
1309 (
1310 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1311 (RootHash::Zero, Label::New, Description::New, ByteSize::New),
1312 ),
1313 (
1314 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1315 (RootHash::Zero, Label::None, Description::None, ByteSize::Some),
1316 ),
1317 (
1318 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1319 (RootHash::Zero, Label::None, Description::Some, ByteSize::Some),
1320 ),
1321 ],
1322 )]
1323 #[test]
1324 fn test_datastore_writer_transition(
1325 with_admin_layer: bool,
1326 with_oracle_layer: bool,
1327 meta_transition: (
1328 (RootHash, Label, Description, ByteSize),
1329 (RootHash, Label, Description, ByteSize),
1330 ),
1331 ) -> anyhow::Result<()> {
1332 let mut sim = Simulator::new();
1333
1334 let [owner, admin, writer] = BlsPair::range();
1335
1336 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
1337 let oracle_fee = 1000;
1338
1339 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1340 let coin = sim.new_coin(owner_puzzle_hash, 1);
1341
1342 let ctx = &mut SpendContext::new();
1343
1344 let admin_delegated_puzzle =
1345 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
1346 let writer_delegated_puzzle =
1347 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
1348 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
1349
1350 let mut delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
1351 delegated_puzzles.push(writer_delegated_puzzle);
1352 if with_admin_layer {
1353 delegated_puzzles.push(admin_delegated_puzzle);
1354 }
1355 if with_oracle_layer {
1356 delegated_puzzles.push(oracle_delegated_puzzle);
1357 }
1358
1359 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1360 ctx,
1361 metadata_from_tuple(meta_transition.0),
1362 owner_puzzle_hash.into(),
1363 delegated_puzzles.clone(),
1364 )?;
1365
1366 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1367
1368 let new_metadata = metadata_from_tuple(meta_transition.1);
1370 let new_metadata_condition = DataStore::new_metadata_condition(ctx, new_metadata)?;
1371
1372 let inner_spend = WriterLayer::new(StandardLayer::new(writer.pk))
1373 .spend(ctx, Conditions::new().with(new_metadata_condition))?;
1374
1375 let new_spend = src_datastore.clone().spend(ctx, inner_spend)?;
1376
1377 let dst_datastore = DataStore::<DataStoreMetadata>::from_spend(
1378 ctx,
1379 &new_spend,
1380 &src_datastore.info.delegated_puzzles,
1381 )?
1382 .unwrap();
1383 ctx.insert(new_spend.clone());
1384
1385 assert_eq!(src_datastore.info.delegated_puzzles, delegated_puzzles);
1386 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1387
1388 assert_eq!(
1389 src_datastore.info.metadata,
1390 metadata_from_tuple(meta_transition.0)
1391 );
1392
1393 assert_delegated_puzzles_contain(
1394 &src_datastore.info.delegated_puzzles,
1395 &[
1396 admin_delegated_puzzle,
1397 writer_delegated_puzzle,
1398 oracle_delegated_puzzle,
1399 ],
1400 &[with_admin_layer, true, with_oracle_layer],
1401 );
1402
1403 assert_eq!(dst_datastore.info.delegated_puzzles, delegated_puzzles);
1404 assert_eq!(dst_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1405
1406 assert_eq!(
1407 dst_datastore.info.metadata,
1408 metadata_from_tuple(meta_transition.1)
1409 );
1410
1411 assert_delegated_puzzles_contain(
1412 &dst_datastore.info.delegated_puzzles,
1413 &[
1414 admin_delegated_puzzle,
1415 writer_delegated_puzzle,
1416 oracle_delegated_puzzle,
1417 ],
1418 &[with_admin_layer, true, with_oracle_layer],
1419 );
1420
1421 sim.spend_coins(ctx.take(), &[owner.sk, admin.sk, writer.sk])?;
1422
1423 let src_coin_state = sim
1424 .coin_state(src_datastore.coin.coin_id())
1425 .expect("expected src datastore coin");
1426 assert_eq!(src_coin_state.coin, src_datastore.coin);
1427 assert!(src_coin_state.spent_height.is_some());
1428 let dst_coin_state = sim
1429 .coin_state(dst_datastore.coin.coin_id())
1430 .expect("expected dst datastore coin");
1431 assert_eq!(dst_coin_state.coin, dst_datastore.coin);
1432 assert!(dst_coin_state.created_height.is_some());
1433
1434 Ok(())
1435 }
1436
1437 #[rstest(
1438 with_admin_layer => [true, false],
1439 with_writer_layer => [true, false],
1440 meta => [
1441 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1442 (RootHash::Zero, Label::None, Description::None, ByteSize::Some),
1443 (RootHash::Zero, Label::None, Description::Some, ByteSize::Some),
1444 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1445 ],
1446 )]
1447 #[test]
1448 fn test_datastore_oracle_transition(
1449 with_admin_layer: bool,
1450 with_writer_layer: bool,
1451 meta: (RootHash, Label, Description, ByteSize),
1452 ) -> anyhow::Result<()> {
1453 let mut sim = Simulator::new();
1454
1455 let [owner, admin, writer, dude] = BlsPair::range();
1456
1457 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
1458 let oracle_fee = 1000;
1459
1460 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1461 let coin = sim.new_coin(owner_puzzle_hash, 1);
1462
1463 let dude_puzzle_hash = StandardArgs::curry_tree_hash(dude.pk).into();
1464
1465 let ctx = &mut SpendContext::new();
1466
1467 let admin_delegated_puzzle =
1468 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
1469 let writer_delegated_puzzle =
1470 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
1471 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
1472
1473 let mut delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
1474 delegated_puzzles.push(oracle_delegated_puzzle);
1475
1476 if with_admin_layer {
1477 delegated_puzzles.push(admin_delegated_puzzle);
1478 }
1479 if with_writer_layer {
1480 delegated_puzzles.push(writer_delegated_puzzle);
1481 }
1482
1483 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1484 ctx,
1485 metadata_from_tuple(meta),
1486 owner_puzzle_hash.into(),
1487 delegated_puzzles.clone(),
1488 )?;
1489
1490 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1491
1492 let inner_datastore_spend = OracleLayer::new(oracle_puzzle_hash, oracle_fee)
1494 .unwrap()
1495 .spend(ctx)?;
1496 let new_spend = src_datastore.clone().spend(ctx, inner_datastore_spend)?;
1497
1498 let dst_datastore =
1499 DataStore::from_spend(ctx, &new_spend, &src_datastore.info.delegated_puzzles)?.unwrap();
1500 ctx.insert(new_spend);
1501
1502 assert_eq!(src_datastore.info, dst_datastore.info);
1503
1504 let mut hasher = Sha256::new();
1506 hasher.update(src_datastore.coin.puzzle_hash);
1507 hasher.update(Bytes::new("$".into()).to_vec());
1508
1509 let new_coin = sim.new_coin(dude_puzzle_hash, oracle_fee);
1510 StandardLayer::new(dude.pk).spend(
1511 ctx,
1512 new_coin,
1513 Conditions::new().assert_puzzle_announcement(Bytes32::new(hasher.finalize())),
1514 )?;
1515
1516 assert_eq!(src_datastore.info.delegated_puzzles, delegated_puzzles);
1519 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1520
1521 assert_eq!(src_datastore.info.metadata, metadata_from_tuple(meta));
1522
1523 assert_delegated_puzzles_contain(
1524 &src_datastore.info.delegated_puzzles,
1525 &[
1526 admin_delegated_puzzle,
1527 writer_delegated_puzzle,
1528 oracle_delegated_puzzle,
1529 ],
1530 &[with_admin_layer, with_writer_layer, true],
1531 );
1532
1533 assert_eq!(dst_datastore.info.delegated_puzzles, delegated_puzzles);
1534 assert_eq!(dst_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1535
1536 assert_eq!(dst_datastore.info.metadata, metadata_from_tuple(meta));
1537
1538 assert_delegated_puzzles_contain(
1539 &dst_datastore.info.delegated_puzzles,
1540 &[
1541 admin_delegated_puzzle,
1542 writer_delegated_puzzle,
1543 oracle_delegated_puzzle,
1544 ],
1545 &[with_admin_layer, with_writer_layer, true],
1546 );
1547
1548 sim.spend_coins(ctx.take(), &[owner.sk, dude.sk])?;
1549
1550 let src_datastore_coin_id = src_datastore.coin.coin_id();
1551 let src_coin_state = sim
1552 .coin_state(src_datastore_coin_id)
1553 .expect("expected src datastore coin");
1554 assert_eq!(src_coin_state.coin, src_datastore.coin);
1555 assert!(src_coin_state.spent_height.is_some());
1556 let dst_coin_state = sim
1557 .coin_state(dst_datastore.coin.coin_id())
1558 .expect("expected dst datastore coin");
1559 assert_eq!(dst_coin_state.coin, dst_datastore.coin);
1560 assert!(dst_coin_state.created_height.is_some());
1561
1562 let oracle_coin = Coin::new(src_datastore_coin_id, oracle_puzzle_hash, oracle_fee);
1563 let oracle_coin_state = sim
1564 .coin_state(oracle_coin.coin_id())
1565 .expect("expected oracle coin");
1566 assert_eq!(oracle_coin_state.coin, oracle_coin);
1567 assert!(oracle_coin_state.created_height.is_some());
1568
1569 Ok(())
1570 }
1571
1572 #[rstest(
1573 with_admin_layer => [true, false],
1574 with_writer_layer => [true, false],
1575 with_oracle_layer => [true, false],
1576 meta => [
1577 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1578 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1579 ],
1580 )]
1581 #[test]
1582 fn test_melt(
1583 with_admin_layer: bool,
1584 with_writer_layer: bool,
1585 with_oracle_layer: bool,
1586 meta: (RootHash, Label, Description, ByteSize),
1587 ) -> anyhow::Result<()> {
1588 let mut sim = Simulator::new();
1589
1590 let [owner, admin, writer] = BlsPair::range();
1591
1592 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
1593 let oracle_fee = 1000;
1594
1595 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1596 let coin = sim.new_coin(owner_puzzle_hash, 1);
1597
1598 let ctx = &mut SpendContext::new();
1599
1600 let admin_delegated_puzzle =
1601 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
1602 let writer_delegated_puzzle =
1603 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
1604 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
1605
1606 let mut delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
1607 if with_admin_layer {
1608 delegated_puzzles.push(admin_delegated_puzzle);
1609 }
1610 if with_writer_layer {
1611 delegated_puzzles.push(writer_delegated_puzzle);
1612 }
1613 if with_oracle_layer {
1614 delegated_puzzles.push(oracle_delegated_puzzle);
1615 }
1616
1617 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1618 ctx,
1619 metadata_from_tuple(meta),
1620 owner_puzzle_hash.into(),
1621 delegated_puzzles.clone(),
1622 )?;
1623
1624 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1625
1626 let output_conds = Conditions::new().melt_singleton();
1628 let inner_datastore_spend =
1629 StandardLayer::new(owner.pk).spend_with_conditions(ctx, output_conds)?;
1630
1631 let new_spend = src_datastore.clone().spend(ctx, inner_datastore_spend)?;
1632 ctx.insert(new_spend);
1633
1634 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1637
1638 assert_eq!(src_datastore.info.metadata, metadata_from_tuple(meta));
1639
1640 assert_delegated_puzzles_contain(
1641 &src_datastore.info.delegated_puzzles,
1642 &[
1643 admin_delegated_puzzle,
1644 writer_delegated_puzzle,
1645 oracle_delegated_puzzle,
1646 ],
1647 &[with_admin_layer, with_writer_layer, with_oracle_layer],
1648 );
1649
1650 sim.spend_coins(ctx.take(), &[owner.sk])?;
1651
1652 let src_coin_state = sim
1653 .coin_state(src_datastore.coin.coin_id())
1654 .expect("expected src datastore coin");
1655 assert_eq!(src_coin_state.coin, src_datastore.coin);
1656 assert!(src_coin_state.spent_height.is_some()); Ok(())
1659 }
1660
1661 enum AttackerPuzzle {
1662 Admin,
1663 Writer,
1664 }
1665
1666 impl AttackerPuzzle {
1667 fn get_spend(
1668 &self,
1669 ctx: &mut SpendContext,
1670 attacker_pk: PublicKey,
1671 output_conds: Conditions,
1672 ) -> Result<Spend, DriverError> {
1673 Ok(match self {
1674 AttackerPuzzle::Admin => {
1675 StandardLayer::new(attacker_pk).spend_with_conditions(ctx, output_conds)?
1676 }
1677
1678 AttackerPuzzle::Writer => {
1679 WriterLayer::new(StandardLayer::new(attacker_pk)).spend(ctx, output_conds)?
1680 }
1681 })
1682 }
1683 }
1684
1685 #[rstest(
1686 puzzle => [AttackerPuzzle::Admin, AttackerPuzzle::Writer],
1687 )]
1688 #[test]
1689 fn test_create_coin_filer(puzzle: AttackerPuzzle) -> anyhow::Result<()> {
1690 let mut sim = Simulator::new();
1691
1692 let [owner, attacker] = BlsPair::range();
1693
1694 let owner_pk = owner.pk;
1695 let attacker_pk = attacker.pk;
1696
1697 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1698 let attacker_puzzle_hash = StandardArgs::curry_tree_hash(attacker.pk);
1699 let coin = sim.new_coin(owner_puzzle_hash, 1);
1700
1701 let ctx = &mut SpendContext::new();
1702
1703 let delegated_puzzle = match puzzle {
1704 AttackerPuzzle::Admin => DelegatedPuzzle::Admin(attacker_puzzle_hash),
1705 AttackerPuzzle::Writer => DelegatedPuzzle::Writer(attacker_puzzle_hash),
1706 };
1707
1708 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1709 ctx,
1710 DataStoreMetadata::default(),
1711 owner_puzzle_hash.into(),
1712 vec![delegated_puzzle],
1713 )?;
1714
1715 StandardLayer::new(owner_pk).spend(ctx, coin, launch_singleton)?;
1716
1717 let inner_datastore_spend = puzzle.get_spend(
1719 ctx,
1720 attacker_pk,
1721 Conditions::new().with(Condition::CreateCoin(CreateCoin {
1722 puzzle_hash: attacker_puzzle_hash.into(),
1723 amount: 1,
1724 memos: Memos::None,
1725 })),
1726 )?;
1727
1728 let new_spend = src_datastore.spend(ctx, inner_datastore_spend)?;
1729
1730 let puzzle_reveal_ptr = ctx.alloc(&new_spend.puzzle_reveal)?;
1731 let solution_ptr = ctx.alloc(&new_spend.solution)?;
1732 match ctx.run(puzzle_reveal_ptr, solution_ptr) {
1733 Ok(_) => panic!("expected error"),
1734 Err(err) => match err {
1735 DriverError::Eval(eval_err) => {
1736 assert_eq!(eval_err.1, "clvm raise");
1737 }
1738 _ => panic!("expected 'clvm raise' error"),
1739 },
1740 }
1741
1742 Ok(())
1743 }
1744
1745 #[rstest(
1746 puzzle => [AttackerPuzzle::Admin, AttackerPuzzle::Writer],
1747 )]
1748 #[test]
1749 fn test_melt_filter(puzzle: AttackerPuzzle) -> anyhow::Result<()> {
1750 let mut sim = Simulator::new();
1751
1752 let [owner, attacker] = BlsPair::range();
1753
1754 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1755 let coin = sim.new_coin(owner_puzzle_hash, 1);
1756
1757 let attacker_puzzle_hash = StandardArgs::curry_tree_hash(attacker.pk);
1758
1759 let ctx = &mut SpendContext::new();
1760
1761 let delegated_puzzle = match puzzle {
1762 AttackerPuzzle::Admin => DelegatedPuzzle::Admin(attacker_puzzle_hash),
1763 AttackerPuzzle::Writer => DelegatedPuzzle::Writer(attacker_puzzle_hash),
1764 };
1765
1766 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1767 ctx,
1768 DataStoreMetadata::default(),
1769 owner_puzzle_hash.into(),
1770 vec![delegated_puzzle],
1771 )?;
1772
1773 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1774
1775 let conds = Conditions::new().melt_singleton();
1777 let inner_datastore_spend = puzzle.get_spend(ctx, attacker.pk, conds)?;
1778
1779 let new_spend = src_datastore.spend(ctx, inner_datastore_spend)?;
1780
1781 let puzzle_reveal_ptr = ctx.alloc(&new_spend.puzzle_reveal)?;
1782 let solution_ptr = ctx.alloc(&new_spend.solution)?;
1783 match ctx.run(puzzle_reveal_ptr, solution_ptr) {
1784 Ok(_) => panic!("expected error"),
1785 Err(err) => match err {
1786 DriverError::Eval(eval_err) => {
1787 assert_eq!(eval_err.1, "clvm raise");
1788 Ok(())
1789 }
1790 _ => panic!("expected 'clvm raise' error"),
1791 },
1792 }
1793 }
1794
1795 #[rstest(
1796 test_puzzle => [AttackerPuzzle::Admin, AttackerPuzzle::Writer],
1797 new_merkle_root => [RootHash::Zero, RootHash::Some],
1798 memos => [vec![], vec![RootHash::Zero], vec![RootHash::Some]],
1799 )]
1800 fn test_new_merkle_root_filter(
1801 test_puzzle: AttackerPuzzle,
1802 new_merkle_root: RootHash,
1803 memos: Vec<RootHash>,
1804 ) -> anyhow::Result<()> {
1805 let attacker = BlsPair::default();
1806
1807 let ctx = &mut SpendContext::new();
1808
1809 let condition_output = Conditions::new().update_data_store_merkle_root(
1810 new_merkle_root.value(),
1811 memos.into_iter().map(|m| m.value().into()).collect(),
1812 );
1813
1814 let spend = test_puzzle.get_spend(ctx, attacker.pk, condition_output)?;
1815
1816 match ctx.run(spend.puzzle, spend.solution) {
1817 Ok(_) => match test_puzzle {
1818 AttackerPuzzle::Admin => Ok(()),
1819 AttackerPuzzle::Writer => panic!("expected error from writer puzzle"),
1820 },
1821 Err(err) => match err {
1822 DriverError::Eval(eval_err) => match test_puzzle {
1823 AttackerPuzzle::Admin => panic!("expected admin puzzle to run normally"),
1824 AttackerPuzzle::Writer => {
1825 assert_eq!(eval_err.1, "clvm raise");
1826 Ok(())
1827 }
1828 },
1829 _ => panic!("other error encountered"),
1830 },
1831 }
1832 }
1833
1834 #[rstest(
1835 puzzle => [AttackerPuzzle::Admin, AttackerPuzzle::Writer],
1836 new_root_hash => [RootHash::Zero, RootHash::Some],
1837 new_updater_ph => [RootHash::Zero.value().into(), DL_METADATA_UPDATER_PUZZLE_HASH],
1838 output_conditions => [false, true],
1839 )]
1840 fn test_metadata_filter(
1841 puzzle: AttackerPuzzle,
1842 new_root_hash: RootHash,
1843 new_updater_ph: TreeHash,
1844 output_conditions: bool,
1845 ) -> anyhow::Result<()> {
1846 let should_error_out =
1847 output_conditions || new_updater_ph != DL_METADATA_UPDATER_PUZZLE_HASH;
1848
1849 let attacker = BlsPair::default();
1850
1851 let ctx = &mut SpendContext::new();
1852
1853 let new_metadata_condition = Condition::update_nft_metadata(
1854 ctx.alloc(&11)?,
1855 ctx.alloc(&NewMetadataOutput {
1856 metadata_info: NewMetadataInfo {
1857 new_metadata: DataStoreMetadata::root_hash_only(new_root_hash.value()),
1858 new_updater_puzzle_hash: new_updater_ph.into(),
1859 },
1860 conditions: if output_conditions {
1861 vec![CreateCoin::<NodePtr> {
1862 puzzle_hash: [0; 32].into(),
1863 amount: 1,
1864 memos: Memos::None,
1865 }]
1866 } else {
1867 vec![]
1868 },
1869 })?,
1870 );
1871
1872 let inner_spend = puzzle.get_spend(
1873 ctx,
1874 attacker.pk,
1875 Conditions::new().with(new_metadata_condition),
1876 )?;
1877
1878 let delegated_puzzles = match puzzle {
1879 AttackerPuzzle::Admin => {
1880 vec![DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(
1881 attacker.pk,
1882 ))]
1883 }
1884 AttackerPuzzle::Writer => vec![DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(
1885 attacker.pk,
1886 ))],
1887 };
1888 let merkle_tree = get_merkle_tree(ctx, delegated_puzzles.clone())?;
1889
1890 let delegation_layer =
1891 DelegationLayer::new(Bytes32::default(), Bytes32::default(), merkle_tree.root());
1892
1893 let puzzle_ptr = delegation_layer.construct_puzzle(ctx)?;
1894
1895 let delegated_puzzle_hash = ctx.tree_hash(inner_spend.puzzle);
1896 let solution_ptr = delegation_layer.construct_solution(
1897 ctx,
1898 DelegationLayerSolution {
1899 merkle_proof: merkle_tree.proof(delegated_puzzle_hash.into()),
1900 puzzle_reveal: inner_spend.puzzle,
1901 puzzle_solution: inner_spend.solution,
1902 },
1903 )?;
1904
1905 match ctx.run(puzzle_ptr, solution_ptr) {
1906 Ok(_) => {
1907 if should_error_out {
1908 panic!("expected puzzle to error out");
1909 } else {
1910 Ok(())
1911 }
1912 }
1913 Err(err) => match err {
1914 DriverError::Eval(eval_err) => {
1915 if should_error_out {
1916 if output_conditions {
1917 assert_eq!(eval_err.1, "= on list");
1918 } else {
1919 assert_eq!(eval_err.1, "clvm raise");
1920 }
1921 Ok(())
1922 } else {
1923 panic!("expected puzzle to not error out");
1924 }
1925 }
1926 _ => panic!("unexpected error while evaluating puzzle"),
1927 },
1928 }
1929 }
1930
1931 #[rstest(
1932 transition => [
1933 (RootHash::Zero, RootHash::Zero, true),
1934 (RootHash::Zero, RootHash::Some, false),
1935 (RootHash::Zero, RootHash::Some, true),
1936 (RootHash::Some, RootHash::Some, true),
1937 (RootHash::Some, RootHash::Some, false),
1938 (RootHash::Some, RootHash::Some, true),
1939 ]
1940 )]
1941 #[test]
1942 fn test_old_memo_format(transition: (RootHash, RootHash, bool)) -> anyhow::Result<()> {
1943 let mut sim = Simulator::new();
1944
1945 let [owner, owner2] = BlsPair::range();
1946
1947 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk);
1948 let coin = sim.new_coin(owner_puzzle_hash.into(), 1);
1949
1950 let owner2_puzzle_hash = StandardArgs::curry_tree_hash(owner2.pk);
1951
1952 let ctx = &mut SpendContext::new();
1953
1954 let launcher = Launcher::new(coin.coin_id(), 1);
1956 let inner_puzzle_hash: TreeHash = owner_puzzle_hash;
1957
1958 let first_root_hash: RootHash = transition.0;
1959 let metadata_ptr = ctx.alloc(&vec![first_root_hash.value()])?;
1960 let metadata_hash = ctx.tree_hash(metadata_ptr);
1961 let state_layer_hash = CurriedProgram {
1962 program: TreeHash::new(NFT_STATE_LAYER_HASH),
1963 args: NftStateLayerArgs::<TreeHash, TreeHash> {
1964 mod_hash: NFT_STATE_LAYER_HASH.into(),
1965 metadata: metadata_hash,
1966 metadata_updater_puzzle_hash: DL_METADATA_UPDATER_PUZZLE_HASH.into(),
1967 inner_puzzle: inner_puzzle_hash,
1968 },
1969 }
1970 .tree_hash();
1971
1972 let kv_list = vec![first_root_hash.value(), owner_puzzle_hash.into()];
1975
1976 let launcher_coin = launcher.coin();
1977 let (launcher_conds, eve_coin) = launcher.spend(ctx, state_layer_hash.into(), kv_list)?;
1978
1979 StandardLayer::new(owner.pk).spend(ctx, coin, launcher_conds)?;
1980
1981 let spends = ctx.take();
1982 spends
1983 .clone()
1984 .into_iter()
1985 .for_each(|spend| ctx.insert(spend));
1986
1987 let datastore_from_launcher = spends
1988 .into_iter()
1989 .find(|spend| spend.coin.coin_id() == eve_coin.parent_coin_info)
1990 .map(|spend| DataStore::from_spend(ctx, &spend, &[]).unwrap().unwrap())
1991 .expect("expected launcher spend");
1992
1993 assert_eq!(
1994 datastore_from_launcher.info.metadata,
1995 DataStoreMetadata::root_hash_only(first_root_hash.value())
1996 );
1997 assert_eq!(
1998 datastore_from_launcher.info.owner_puzzle_hash,
1999 owner_puzzle_hash.into()
2000 );
2001 assert!(datastore_from_launcher.info.delegated_puzzles.is_empty());
2002
2003 assert_eq!(
2004 datastore_from_launcher.info.launcher_id,
2005 eve_coin.parent_coin_info
2006 );
2007 assert_eq!(datastore_from_launcher.coin.coin_id(), eve_coin.coin_id());
2008
2009 match datastore_from_launcher.proof {
2010 Proof::Eve(proof) => {
2011 assert_eq!(
2012 proof.parent_parent_coin_info,
2013 launcher_coin.parent_coin_info
2014 );
2015 assert_eq!(proof.parent_amount, launcher_coin.amount);
2016 }
2017 Proof::Lineage(_) => panic!("expected eve (not lineage) proof for info_from_launcher"),
2018 }
2019
2020 let mut inner_spend_conditions = Conditions::new();
2023
2024 let second_root_hash: RootHash = transition.1;
2025
2026 let new_metadata = DataStoreMetadata::root_hash_only(second_root_hash.value());
2027 if second_root_hash != first_root_hash {
2028 inner_spend_conditions = inner_spend_conditions.with(
2029 DataStore::new_metadata_condition(ctx, new_metadata.clone())?,
2030 );
2031 }
2032
2033 let new_owner: bool = transition.2;
2034 let new_inner_ph: Bytes32 = if new_owner {
2035 owner2_puzzle_hash.into()
2036 } else {
2037 owner_puzzle_hash.into()
2038 };
2039
2040 inner_spend_conditions = inner_spend_conditions.with(Condition::CreateCoin(CreateCoin {
2043 puzzle_hash: new_inner_ph,
2044 amount: 1,
2045 memos: ctx.memos(&[
2046 launcher_coin.coin_id(),
2047 second_root_hash.value(),
2048 new_inner_ph,
2049 ])?,
2050 }));
2051
2052 let inner_spend =
2053 StandardLayer::new(owner.pk).spend_with_conditions(ctx, inner_spend_conditions)?;
2054 let spend = datastore_from_launcher.clone().spend(ctx, inner_spend)?;
2055
2056 let new_datastore = DataStore::<DataStoreMetadata>::from_spend(
2057 ctx,
2058 &spend,
2059 &datastore_from_launcher.info.delegated_puzzles,
2060 )?
2061 .unwrap();
2062
2063 assert_eq!(
2064 new_datastore.info.metadata,
2065 DataStoreMetadata::root_hash_only(second_root_hash.value())
2066 );
2067
2068 assert!(new_datastore.info.delegated_puzzles.is_empty());
2069
2070 assert_eq!(new_datastore.info.owner_puzzle_hash, new_inner_ph);
2071 assert_eq!(new_datastore.info.launcher_id, eve_coin.parent_coin_info);
2072
2073 assert_eq!(
2074 new_datastore.coin.parent_coin_info,
2075 datastore_from_launcher.coin.coin_id()
2076 );
2077 assert_eq!(
2078 new_datastore.coin.puzzle_hash,
2079 SingletonArgs::curry_tree_hash(
2080 datastore_from_launcher.info.launcher_id,
2081 CurriedProgram {
2082 program: TreeHash::new(NFT_STATE_LAYER_HASH),
2083 args: NftStateLayerArgs::<TreeHash, DataStoreMetadata> {
2084 mod_hash: NFT_STATE_LAYER_HASH.into(),
2085 metadata: new_metadata,
2086 metadata_updater_puzzle_hash: DL_METADATA_UPDATER_PUZZLE_HASH.into(),
2087 inner_puzzle: new_inner_ph.into(),
2088 },
2089 }
2090 .tree_hash()
2091 )
2092 .into()
2093 );
2094 assert_eq!(new_datastore.coin.amount, 1);
2095
2096 match new_datastore.proof {
2097 Proof::Lineage(proof) => {
2098 assert_eq!(proof.parent_parent_coin_info, eve_coin.parent_coin_info);
2099 assert_eq!(proof.parent_amount, eve_coin.amount);
2100 assert_eq!(
2101 proof.parent_inner_puzzle_hash,
2102 CurriedProgram {
2103 program: TreeHash::new(NFT_STATE_LAYER_HASH),
2104 args: NftStateLayerArgs::<TreeHash, DataStoreMetadata> {
2105 mod_hash: NFT_STATE_LAYER_HASH.into(),
2106 metadata: datastore_from_launcher.info.metadata,
2107 metadata_updater_puzzle_hash: DL_METADATA_UPDATER_PUZZLE_HASH.into(),
2108 inner_puzzle: owner_puzzle_hash,
2109 },
2110 }
2111 .tree_hash()
2112 .into()
2113 );
2114 }
2115 Proof::Eve(_) => panic!("expected lineage (not eve) proof for new_info"),
2116 }
2117
2118 ctx.insert(spend);
2119
2120 sim.spend_coins(ctx.take(), &[owner.sk, owner2.sk])?;
2121
2122 let eve_coin_state = sim
2123 .coin_state(eve_coin.coin_id())
2124 .expect("expected eve coin");
2125 assert!(eve_coin_state.created_height.is_some());
2126
2127 Ok(())
2128 }
2129}