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